cim 0.3.6

CDS Implementation generation by a Metadata document
Documentation
use super::super::error::ParserError;
use std::collections::HashMap;
use std::error::Error;
use std::fmt::{Display, Formatter, Result as FMTResult};

#[derive(Clone)]
pub struct Field {
  is_key: bool,
  name: String,
  cds_type: CDSType,
}

impl Field {
  pub fn from_odata(
    name: &str,
    odata_type: &str,
    attributes: &HashMap<String, String>,
  ) -> Result<Self, Box<dyn Error>> {
    Ok(Field::new(
      name.to_owned(),
      CDSType::from_odata(odata_type.to_owned(), attributes)?,
    ))
  }

  pub fn new_association(name: &str, target: &str) -> Self {
    Field::new(
      name.to_owned(),
      CDSType::Association {
        target: target.to_owned(),
      },
    )
  }

  fn new(name: String, cds_type: CDSType) -> Self {
    Field {
      name,
      cds_type,
      is_key: false,
    }
  }

  pub fn to_cds(&self) -> String {
    if self.is_key {
      format!("key {}: {};\n", self.name, self.cds_type)
    } else {
      format!("{}: {};\n", self.name, self.cds_type)
    }
  }

  pub fn set_as_key(&mut self) {
    self.is_key = true;
  }
}

#[derive(Clone, Debug)]
enum CDSType {
  Uuid,
  Boolean,
  Integer,
  Integer64,
  Decimal { precision: String, scale: String },
  Double,
  Date,
  Time,
  DateTime,
  String { length: Option<String> },
  Binary,
  Single,
  Byte,
  SByte,
  Stream,
  Association { target: String },
}

impl Display for CDSType {
  fn fmt(&self, fmt: &mut Formatter) -> FMTResult {
    let type_string = match self {
      CDSType::Uuid => String::from("UUID"),
      CDSType::Boolean => String::from("Boolean"),
      CDSType::Integer => String::from("Integer"),
      CDSType::Integer64 => String::from("Integer64"),
      CDSType::Decimal { precision, scale } => format!("Decimal({precision}, {scale})"),
      CDSType::Double => String::from("Double"),
      CDSType::Date => String::from("Date"),
      CDSType::Time => String::from("Time"),
      CDSType::DateTime => String::from("DateTime"),
      CDSType::String { length } => match length {
        Some(length) => format!("String({length})"),
        None => "String".to_string(),
      },
      CDSType::Binary => String::from("Binary"),
      CDSType::Single => String::from("Double @odata.Type: 'Edm.Single'"),
      CDSType::Byte => String::from("Integer @odata.Type: 'Edm.Byte'"),
      CDSType::SByte => String::from("Integer @odata.Type: 'Edm.SByte'"),
      CDSType::Stream => String::from("LargeBinary @odata.Type: 'Edm.Stream'"),
      CDSType::Association { target } => format!("Association to {target} on ..."),
    };

    write!(fmt, "{}", type_string)
  }
}

impl CDSType {
  fn from_odata(
    odata_type: String,
    attributes: &HashMap<String, String>,
  ) -> Result<Self, Box<dyn Error>> {
    match odata_type.as_str() {
      "Edm.Guid" => Ok(Self::Uuid),
      "Edm.Boolean" => Ok(Self::Boolean),
      "Edm.Int16" => Ok(Self::Integer),
      "Edm.Int32" => Ok(Self::Integer),
      "Edm.Int64" => Ok(Self::Integer64),
      "Edm.Decimal" => {
        let scale = attributes.get("scale").cloned();
        let scale = scale.or_else(|| attributes.get("Scale").cloned());
        let precision = attributes.get("precision").cloned();
        let precision = precision.or_else(|| attributes.get("Precision").cloned());
        match (scale, precision) {
          (Some(scale), Some(precision)) => Ok(Self::Decimal { scale, precision }),
          _ => Err(ParserError::new_boxed(
            "Failed to parse a Decimal type, scale or precision is missing",
          )),
        }
      }
      "Edm.Double" => Ok(Self::Double),
      "Edm.Date" => Ok(Self::Date),
      "Edm.TimeOfDay" | "Edm.Time" => Ok(Self::Time),
      "Edm.DateTime" | "Edm.DateTimeOffset" => Ok(Self::DateTime),
      "Edm.String" => {
        let length = attributes.get("MaxLength").cloned();
        Ok(Self::String { length })
      }
      "Edm.Binary" => Ok(Self::Binary),
      "Edm.Single" => Ok(Self::Single),
      "Edm.Byte" => Ok(Self::Byte),
      "Edm.SByte" => Ok(Self::SByte),
      "Edm.Stream" => Ok(Self::Stream),
      _ => Err(ParserError::new_boxed(format!(
        "Unknown/Unsupported OData Type '{odata_type}'"
      ))),
    }
  }
}