/* * Copyright 2022 Fluence Labs Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ use super::{ServiceDefinition, ServiceTagName}; use crate::services::JValue; use air_test_utils::CallServiceResult; use nom::{error::VerboseError, IResult, InputTakeAtPosition, Parser}; use std::{collections::HashMap, str::FromStr}; type ParseError<'inp> = VerboseError<&'inp str>; impl FromStr for ServiceDefinition { type Err = String; fn from_str(s: &str) -> Result { nom::combinator::all_consuming(parse_kw)(s) .map(|(_, service_definition)| service_definition) .map_err(|e| e.to_string()) } } // kw "=" val // example: "id=firstcall" pub fn parse_kw(inp: &str) -> IResult<&str, ServiceDefinition, ParseError> { use nom::branch::alt; use nom::bytes::complete::tag; use nom::combinator::{cut, map_res, rest}; use nom::error::context; use nom::sequence::separated_pair; let equal = || delim_ws(tag("=")); delim_ws(map_res( separated_pair( alt(( tag(ServiceTagName::Ok.as_ref()), tag(ServiceTagName::Error.as_ref()), tag(ServiceTagName::SeqResult.as_ref()), tag(ServiceTagName::Behaviour.as_ref()), )), equal(), cut(context( "result value is consumed to end and has to be a valid JSON", rest, )), ), |(tag, value): (&str, &str)| { let value = value.trim(); match ServiceTagName::from_str(tag) { Ok(ServiceTagName::Ok) => { serde_json::from_str::(value).map(ServiceDefinition::Ok) } Ok(ServiceTagName::Error) => { serde_json::from_str::(value).map(ServiceDefinition::Error) } Ok(ServiceTagName::SeqResult) => { serde_json::from_str::>(value) .map(ServiceDefinition::SeqResult) } Ok(ServiceTagName::Behaviour) => Ok(ServiceDefinition::Behaviour(value.to_owned())), Err(_) => unreachable!("unknown tag {:?}", tag), } }, ))(inp) } pub(crate) fn delim_ws(f: F) -> impl FnMut(I) -> IResult where F: Parser, E: nom::error::ParseError, I: InputTakeAtPosition, ::Item: nom::AsChar + Clone, { use nom::character::complete::multispace0; use nom::sequence::delimited; delimited(multispace0, f, multispace0) } #[cfg(test)] mod tests { use super::*; #[test] fn test_parse_empty() { let res = ServiceDefinition::from_str(""); assert!(res.is_err()); } #[test] fn test_parse_garbage0() { let res = ServiceDefinition::from_str("garbage"); assert!(res.is_err(), "{:?}", res); } #[test] fn test_result_service() { use serde_json::json; let res = ServiceDefinition::from_str(r#"ok={"this":["is","value"]}"#); assert_eq!( res, Ok(ServiceDefinition::Ok(json!({"this": ["is", "value"]}))), ); } #[test] fn test_result_service_malformed() { let res = ServiceDefinition::from_str(r#"ok={"this":["is","value"]"#); assert!(res.is_err()); } #[test] fn test_call_result() { use serde_json::json; let res = ServiceDefinition::from_str(r#"err={"ret_code": 0, "result": [1, 2, 3]}"#); assert_eq!( res, Ok(ServiceDefinition::Error(CallServiceResult::ok(json!([ 1, 2, 3 ])))), ); } #[test] fn test_call_result_malformed() { let res = ServiceDefinition::from_str(r#"err={"retcode": 0, "result": [1, 2, 3]}"#); assert!(res.is_err()); } #[test] fn test_call_result_invalid() { let res = ServiceDefinition::from_str(r#"err={"ret_code": 0, "result": 1, 2, 3]}"#); assert!(res.is_err()); } #[test] fn test_seq_result() { use serde_json::json; let res = ServiceDefinition::from_str(r#"seq_result={"default": 42, "1": true, "3": []}"#); assert_eq!( res, Ok(ServiceDefinition::SeqResult(maplit::hashmap! { "default".to_owned() => json!(42), "1".to_owned() => json!(true), "3".to_owned() => json!([]), })), ); } #[test] fn test_seq_result_malformed() { let res = ServiceDefinition::from_str(r#"seq_result={"default": 42, "1": true, "3": ]}"#); assert!(res.is_err()); } #[test] fn test_seq_result_invalid() { // TODO perhaps, we should support both arrays and maps let res = ServiceDefinition::from_str(r#"seq_result=[42, 43]"#); assert!(res.is_err()); } #[test] fn test_behaviour() { let res = ServiceDefinition::from_str(r#"behaviour=echo"#); assert_eq!(res, Ok(ServiceDefinition::Behaviour("echo".to_owned())),); } }