Skip to main content

nv_redfish_bmc_mock/
lib.rs

1// SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2// SPDX-License-Identifier: Apache-2.0
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16pub mod expect;
17
18#[doc(inline)]
19pub use expect::Expect;
20pub use expect::ExpectedRequest;
21
22use nv_redfish_core::action::ActionTarget;
23use nv_redfish_core::query::ExpandQuery;
24use nv_redfish_core::ActionError;
25use nv_redfish_core::Bmc as NvRedfishBmc;
26use nv_redfish_core::Expandable;
27use nv_redfish_core::ModificationResponse;
28use nv_redfish_core::ODataETag;
29use nv_redfish_core::ODataId;
30use serde::Serialize;
31use serde_json::from_value;
32use serde_json::to_value;
33use serde_json::Error as JsonError;
34use std::collections::VecDeque;
35use std::error::Error as StdError;
36use std::fmt::Display;
37use std::fmt::Formatter;
38use std::fmt::Result as FmtResult;
39use std::sync::Arc;
40use std::sync::Mutex;
41use std::sync::PoisonError;
42
43#[derive(Debug)]
44pub enum Error {
45    NotSupported,
46    ErrorResponse(Box<dyn StdError + Send + Sync>),
47    MutexLock(String),
48    NothingIsExpected,
49    BadResponseJson(JsonError),
50    UnexpectedGet(ODataId, ExpectedRequest),
51    UnexpectedExpand(ODataId, ExpectedRequest),
52    UnexpectedUpdate(ODataId, String, ExpectedRequest),
53    UnexpectedCreate(ODataId, String, ExpectedRequest),
54    UnexpectedDelete(ODataId, ExpectedRequest),
55    UnexpectedAction(ActionTarget, String, ExpectedRequest),
56    UnexpectedStream(String, ExpectedRequest),
57}
58
59impl Display for Error {
60    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
61        match self {
62            Self::ErrorResponse(err) => write!(f, "response: {err}"),
63            Self::NotSupported => write!(f, "not supported"),
64            Self::MutexLock(err) => write!(f, "lock error: {err}"),
65            Self::NothingIsExpected => {
66                write!(f, "nothing is expected to happen but something happened")
67            }
68            Self::BadResponseJson(err) => write!(f, "bad json response: {err}"),
69            Self::UnexpectedGet(id, expected) => {
70                write!(f, "unexpected get: {id}; expected: {expected:?}")
71            }
72            Self::UnexpectedExpand(id, expected) => {
73                write!(f, "unexpected expand: {id}; expected: {expected:?}")
74            }
75            Self::UnexpectedUpdate(id, json, expected) => {
76                write!(
77                    f,
78                    "unexpected update: {id}; json: {json} expected: {expected:?}"
79                )
80            }
81            Self::UnexpectedCreate(id, json, expected) => {
82                write!(
83                    f,
84                    "unexpected create: {id}; json: {json} expected: {expected:?}"
85                )
86            }
87            Self::UnexpectedDelete(id, expected) => {
88                write!(f, "unexpected delete: {id}; expected: {expected:?}")
89            }
90            Self::UnexpectedAction(id, json, expected) => {
91                write!(
92                    f,
93                    "unexpected action: {id}; json: {json} expected: {expected:?}"
94                )
95            }
96            Self::UnexpectedStream(uri, expected) => {
97                write!(f, "unexpected stream: {uri}; expected: {expected:?}")
98            }
99        }
100    }
101}
102
103impl StdError for Error {}
104
105impl Error {
106    pub fn mutex_lock<T>(err: PoisonError<T>) -> Self {
107        Self::MutexLock(err.to_string())
108    }
109}
110
111#[derive(Default)]
112pub struct Bmc<E> {
113    expect: Mutex<VecDeque<Expect<E>>>,
114}
115
116impl<E> Bmc<E> {
117    pub fn expect(&self, exp: Expect<E>) {
118        let expect: &mut VecDeque<Expect<E>> = &mut self.expect.lock().expect("not poisoned");
119        expect.push_back(exp);
120    }
121
122    pub fn debug_expect(&self) {
123        let expect: &VecDeque<Expect<E>> = &self.expect.lock().expect("not poisoned");
124        println!("Expectations (total: {})", expect.len());
125        for v in expect {
126            println!("{:#?}", v.request);
127        }
128    }
129}
130
131impl<E> NvRedfishBmc for Bmc<E>
132where
133    E: StdError + Send + Sync + 'static,
134{
135    type Error = Error;
136
137    async fn expand<T>(&self, in_id: &ODataId, _query: ExpandQuery) -> Result<Arc<T>, Error>
138    where
139        T: Expandable,
140    {
141        let expect = self
142            .expect
143            .lock()
144            .map_err(Error::mutex_lock)?
145            .pop_front()
146            .ok_or(Error::NothingIsExpected)?;
147        match expect {
148            Expect {
149                request: ExpectedRequest::Expand { id },
150                response,
151            } if id == *in_id => {
152                let response = response.map_err(|err| Error::ErrorResponse(Box::new(err)))?;
153                let result: T = from_value(response).map_err(Error::BadResponseJson)?;
154                Ok(Arc::new(result))
155            }
156            _ => Err(Error::UnexpectedExpand(in_id.clone(), expect.request)),
157        }
158    }
159
160    async fn get<T: nv_redfish_core::EntityTypeRef + Sized + for<'a> serde::Deserialize<'a>>(
161        &self,
162        in_id: &ODataId,
163    ) -> Result<Arc<T>, Self::Error> {
164        let expect = self
165            .expect
166            .lock()
167            .map_err(Error::mutex_lock)?
168            .pop_front()
169            .ok_or(Error::NothingIsExpected)?;
170        match expect {
171            Expect {
172                request: ExpectedRequest::Get { id },
173                response,
174            } if id == *in_id => {
175                let response = response.map_err(|err| Error::ErrorResponse(Box::new(err)))?;
176                let result: T = from_value(response).map_err(Error::BadResponseJson)?;
177                Ok(Arc::new(result))
178            }
179            _ => Err(Error::UnexpectedGet(in_id.clone(), expect.request)),
180        }
181    }
182
183    async fn update<
184        V: Sync + Send + Serialize,
185        R: Sync + Send + Sized + for<'a> serde::Deserialize<'a>,
186    >(
187        &self,
188        in_id: &ODataId,
189        _etag: Option<&ODataETag>,
190        update: &V,
191    ) -> Result<ModificationResponse<R>, Self::Error> {
192        let expect = self
193            .expect
194            .lock()
195            .map_err(Error::mutex_lock)?
196            .pop_front()
197            .ok_or(Error::NothingIsExpected)?;
198        let in_request = to_value(update).expect("json serializable");
199        match expect {
200            Expect {
201                request: ExpectedRequest::Update { id, request },
202                response,
203            } if id == *in_id && request == in_request => {
204                let response = response.map_err(|err| Error::ErrorResponse(Box::new(err)))?;
205                let result: R = from_value(response).map_err(Error::BadResponseJson)?;
206                Ok(ModificationResponse::Entity(result))
207            }
208            _ => Err(Error::UnexpectedUpdate(
209                in_id.clone(),
210                in_request.to_string(),
211                expect.request,
212            )),
213        }
214    }
215
216    async fn create<
217        V: Sync + Send + Serialize,
218        R: Sync + Send + Sized + for<'a> serde::Deserialize<'a>,
219    >(
220        &self,
221        in_id: &ODataId,
222        create: &V,
223    ) -> Result<ModificationResponse<R>, Self::Error> {
224        let expect = self
225            .expect
226            .lock()
227            .map_err(Error::mutex_lock)?
228            .pop_front()
229            .ok_or(Error::NothingIsExpected)?;
230        let in_request = to_value(create).expect("json serializable");
231        match expect {
232            Expect {
233                request: ExpectedRequest::Create { id, request },
234                response,
235            } if id == *in_id && request == in_request => {
236                let response = response.map_err(|err| Error::ErrorResponse(Box::new(err)))?;
237                let result: R = from_value(response).map_err(Error::BadResponseJson)?;
238                Ok(ModificationResponse::Entity(result))
239            }
240            _ => Err(Error::UnexpectedCreate(
241                in_id.clone(),
242                in_request.to_string(),
243                expect.request,
244            )),
245        }
246    }
247
248    async fn delete<
249        R: nv_redfish_core::EntityTypeRef + Sync + Send + for<'de> serde::Deserialize<'de>,
250    >(
251        &self,
252        in_id: &ODataId,
253    ) -> Result<ModificationResponse<R>, Self::Error> {
254        let expect = self
255            .expect
256            .lock()
257            .map_err(Error::mutex_lock)?
258            .pop_front()
259            .ok_or(Error::NothingIsExpected)?;
260        match expect {
261            Expect {
262                request: ExpectedRequest::Delete { id },
263                ..
264            } if id == *in_id => Ok(ModificationResponse::Empty),
265            _ => Err(Error::UnexpectedDelete(in_id.clone(), expect.request)),
266        }
267    }
268
269    async fn action<
270        T: Send + Sync + serde::Serialize,
271        R: Send + Sync + Sized + for<'a> serde::Deserialize<'a>,
272    >(
273        &self,
274        action: &nv_redfish_core::Action<T, R>,
275        params: &T,
276    ) -> Result<ModificationResponse<R>, Self::Error> {
277        let expect = self
278            .expect
279            .lock()
280            .map_err(Error::mutex_lock)?
281            .pop_front()
282            .ok_or(Error::NothingIsExpected)?;
283        let in_request = to_value(params).expect("json serializable");
284        match expect {
285            Expect {
286                request: ExpectedRequest::Action { target, request },
287                response,
288            } if target == action.target && request == in_request => {
289                let response = response.map_err(|err| Error::ErrorResponse(Box::new(err)))?;
290                let result: R = from_value(response).map_err(Error::BadResponseJson)?;
291                Ok(ModificationResponse::Entity(result))
292            }
293            _ => Err(Error::UnexpectedAction(
294                action.target.clone(),
295                in_request.to_string(),
296                expect.request,
297            )),
298        }
299    }
300
301    async fn filter<
302        T: nv_redfish_core::EntityTypeRef
303            + Sized
304            + for<'a> serde::Deserialize<'a>
305            + 'static
306            + Send
307            + Sync,
308    >(
309        &self,
310        _id: &ODataId,
311        _query: nv_redfish_core::FilterQuery,
312    ) -> Result<Arc<T>, Self::Error> {
313        todo!("unimplemented")
314    }
315
316    async fn stream<T: Sized + for<'a> serde::Deserialize<'a> + Send + 'static>(
317        &self,
318        in_uri: &str,
319    ) -> Result<nv_redfish_core::BoxTryStream<T, Self::Error>, Self::Error> {
320        let expect = self
321            .expect
322            .lock()
323            .map_err(Error::mutex_lock)?
324            .pop_front()
325            .ok_or(Error::NothingIsExpected)?;
326        match expect {
327            Expect {
328                request: ExpectedRequest::Stream { uri },
329                response,
330            } if uri == *in_uri => {
331                let response = response.map_err(|err| Error::ErrorResponse(Box::new(err)))?;
332                let result: Vec<T> = from_value(response).map_err(Error::BadResponseJson)?;
333                Ok(Box::pin(futures_util::stream::iter(
334                    result.into_iter().map(Ok),
335                )))
336            }
337            _ => Err(Error::UnexpectedStream(in_uri.to_string(), expect.request)),
338        }
339    }
340}
341
342impl ActionError for Error {
343    fn not_supported() -> Self {
344        Error::NotSupported
345    }
346}