use std::{collections::HashMap, fmt::Display, hash::Hash};
use serde::{ser::SerializeMap, Serialize};
use crate::tfplugin6;
#[derive(Clone, PartialEq, Eq, Hash, Debug, Default)]
pub enum StringKind {
#[default]
Plain = 0,
Markdown = 1,
}
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub struct Description {
pub kind: StringKind,
pub content: String,
}
impl Description {
pub fn plain<T>(content: T) -> Self
where
T: ToString,
{
Self {
kind: StringKind::Plain,
content: content.to_string(),
}
}
pub fn markdown<T>(content: T) -> Self
where
T: ToString,
{
Self {
kind: StringKind::Markdown,
content: content.to_string(),
}
}
}
impl<T> From<T> for Description
where
T: ToString,
{
fn from(value: T) -> Self {
Description::plain(value.to_string())
}
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum NestedBlock {
Single(Block),
List(Block),
Set(Block),
Map(Block),
Group(Block),
Optional(Block),
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Block {
pub version: i64,
pub attributes: HashMap<String, Attribute>,
pub blocks: HashMap<String, NestedBlock>,
pub description: Description,
pub deprecated: bool,
}
impl Default for Block {
fn default() -> Block {
Block {
version: 1,
attributes: Default::default(),
blocks: Default::default(),
description: "empty".into(),
deprecated: false,
}
}
}
fn cvt_nested_blocks_tf6(
blocks: &HashMap<String, NestedBlock>,
) -> ::prost::alloc::vec::Vec<tfplugin6::schema::NestedBlock> {
use tfplugin6::schema::nested_block::NestingMode;
blocks
.iter()
.map(|(name, nested_block)| {
let (nesting_mode, block) = match nested_block {
NestedBlock::Single(block) => (NestingMode::Single, block),
NestedBlock::List(block) => (NestingMode::List, block),
NestedBlock::Set(block) => (NestingMode::Set, block),
NestedBlock::Map(block) => (NestingMode::Map, block),
NestedBlock::Group(block) => (NestingMode::Group, block),
NestedBlock::Optional(block) => (NestingMode::List, block),
};
let nitems = match nested_block {
NestedBlock::Single(_) => (1, 1),
NestedBlock::List(_) => (0, i64::MAX),
NestedBlock::Set(_) => (0, i64::MAX),
NestedBlock::Map(_) => (0, 0),
NestedBlock::Group(_) => (0, 0),
NestedBlock::Optional(_) => (0, 1),
};
tfplugin6::schema::NestedBlock {
type_name: name.clone(),
block: Some(block.into()),
nesting: nesting_mode as i32,
min_items: nitems.0,
max_items: nitems.1,
}
})
.collect()
}
#[allow(deprecated)]
fn cvt_attributes_tf6(
attrs: &HashMap<String, Attribute>,
) -> ::prost::alloc::vec::Vec<tfplugin6::schema::Attribute> {
use tfplugin6::schema::object::NestingMode;
use tfplugin6::schema::Object;
attrs
.iter()
.map(|(name, attr)| {
let attr_type = attr.attr_type.to_string().into();
let nested = match &attr.attr_type {
AttributeType::AttributeSingle(attrs) => Some((NestingMode::Single, attrs)),
AttributeType::AttributeList(attrs) => Some((NestingMode::List, attrs)),
AttributeType::AttributeSet(attrs) => Some((NestingMode::Set, attrs)),
AttributeType::AttributeMap(attrs) => Some((NestingMode::Map, attrs)),
_ => None,
}
.map(|(nesting_mode, attrs)| Object {
attributes: cvt_attributes_tf6(attrs),
nesting: nesting_mode as i32,
min_items: 0,
max_items: if nesting_mode == NestingMode::Single {
1
} else {
i64::MAX
},
});
tfplugin6::schema::Attribute {
name: name.clone(),
r#type: attr_type,
nested_type: nested,
description: attr.description.content.clone(),
required: attr.constraint == AttributeConstraint::Required,
optional: attr.constraint == AttributeConstraint::OptionalComputed
|| attr.constraint == AttributeConstraint::Optional,
computed: attr.constraint == AttributeConstraint::OptionalComputed
|| attr.constraint == AttributeConstraint::Computed,
sensitive: attr.sensitive,
description_kind: match attr.description.kind {
StringKind::Plain => tfplugin6::StringKind::Plain,
StringKind::Markdown => tfplugin6::StringKind::Markdown,
} as i32,
deprecated: attr.deprecated,
}
})
.collect()
}
impl From<&Block> for tfplugin6::schema::Block {
fn from(value: &Block) -> Self {
Self {
attributes: cvt_attributes_tf6(&value.attributes),
block_types: cvt_nested_blocks_tf6(&value.blocks),
version: value.version,
description: value.description.content.clone(),
description_kind: match value.description.kind {
StringKind::Plain => tfplugin6::StringKind::Plain,
StringKind::Markdown => tfplugin6::StringKind::Markdown,
} as i32,
deprecated: value.deprecated,
}
}
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum AttributeType {
String,
Number,
Bool,
List(Box<AttributeType>),
Set(Box<AttributeType>),
Map(Box<AttributeType>),
Object(HashMap<String, AttributeType>),
Tuple(Vec<AttributeType>),
AttributeSingle(HashMap<String, Attribute>),
AttributeList(HashMap<String, Attribute>),
AttributeSet(HashMap<String, Attribute>),
AttributeMap(HashMap<String, Attribute>),
Any,
}
impl Serialize for AttributeType {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
struct AttributesAsType<'a>(&'a HashMap<String, Attribute>);
impl<'a> Serialize for AttributesAsType<'a> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut map = serializer.serialize_map(Some(self.0.len()))?;
for (name, attr) in self.0 {
map.serialize_entry(name, &attr.attr_type)?;
}
map.end()
}
}
match self {
AttributeType::String => serializer.serialize_str("string"),
AttributeType::Number => serializer.serialize_str("number"),
AttributeType::Bool => serializer.serialize_str("bool"),
AttributeType::List(attr) => ("list", attr).serialize(serializer),
AttributeType::Set(attr) => ("set", attr).serialize(serializer),
AttributeType::Map(attr) => ("map", attr).serialize(serializer),
AttributeType::Object(attrs) => ("object", attrs).serialize(serializer),
AttributeType::Tuple(attrs) => ("tuple", attrs).serialize(serializer),
AttributeType::AttributeSingle(attrs) => {
("object", &AttributesAsType(attrs)).serialize(serializer)
}
AttributeType::AttributeList(attrs) => {
("list", ("object", &AttributesAsType(attrs))).serialize(serializer)
}
AttributeType::AttributeSet(attrs) => {
("set", ("object", &AttributesAsType(attrs))).serialize(serializer)
}
AttributeType::AttributeMap(attrs) => {
("map", ("object", &AttributesAsType(attrs))).serialize(serializer)
}
AttributeType::Any => serializer.serialize_str("dynamic"),
}
}
}
impl Display for AttributeType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
return f.write_str(
serde_json::to_string(self)
.or(Err(std::fmt::Error))?
.as_str(),
);
}
}
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub enum AttributeConstraint {
Computed,
Optional,
OptionalComputed,
Required,
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Attribute {
pub attr_type: AttributeType,
pub description: Description,
pub constraint: AttributeConstraint,
pub sensitive: bool,
pub deprecated: bool,
}
impl Default for Attribute {
fn default() -> Self {
Self {
attr_type: AttributeType::Any,
description: "empty".into(),
constraint: AttributeConstraint::OptionalComputed,
sensitive: false,
deprecated: false,
}
}
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Schema {
pub version: i64,
pub block: Block,
}
impl From<&Schema> for tfplugin6::Schema {
fn from(value: &Schema) -> Self {
Self {
version: value.version,
block: Some((&value.block).into()),
}
}
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum Type {
String,
Number,
Bool,
List(Box<Type>),
Set(Box<Type>),
Map(Box<Type>),
Object(HashMap<String, Type>),
Tuple(Vec<Type>),
Any,
}
impl Serialize for Type {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match self {
Type::String => serializer.serialize_str("string"),
Type::Number => serializer.serialize_str("number"),
Type::Bool => serializer.serialize_str("bool"),
Type::List(attr) => ("list", attr).serialize(serializer),
Type::Set(attr) => ("set", attr).serialize(serializer),
Type::Map(attr) => ("map", attr).serialize(serializer),
Type::Object(attrs) => ("object", attrs).serialize(serializer),
Type::Tuple(attrs) => ("tuple", attrs).serialize(serializer),
Type::Any => serializer.serialize_str("dynamic"),
}
}
}
impl Display for Type {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
return f.write_str(
serde_json::to_string(self)
.or(Err(std::fmt::Error))?
.as_str(),
);
}
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Parameter {
pub name: String,
pub param_type: Type,
pub allow_null: bool,
pub allow_unknown: bool,
pub description: Description,
}
impl Default for Parameter {
fn default() -> Self {
Self {
name: Default::default(),
param_type: Type::Any,
allow_null: Default::default(),
allow_unknown: Default::default(),
description: Description::plain(""),
}
}
}
impl From<&Parameter> for tfplugin6::function::Parameter {
fn from(value: &Parameter) -> Self {
Self {
name: value.name.clone(),
r#type: value.param_type.to_string().into(),
allow_null_value: value.allow_null,
allow_unknown_values: value.allow_unknown,
description: value.description.content.clone(),
description_kind: match value.description.kind {
StringKind::Markdown => tfplugin6::StringKind::Markdown,
StringKind::Plain => tfplugin6::StringKind::Plain,
} as i32,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FunctionSchema {
pub parameters: Vec<Parameter>,
pub variadic: Option<Parameter>,
pub return_type: Type,
pub summary: String,
pub description: Description,
pub deprecated: Option<String>,
}
impl Default for FunctionSchema {
fn default() -> Self {
Self {
parameters: Default::default(),
variadic: Default::default(),
return_type: Type::Any,
summary: Default::default(),
description: Description::plain(""),
deprecated: Default::default(),
}
}
}
impl From<&FunctionSchema> for tfplugin6::Function {
fn from(value: &FunctionSchema) -> Self {
Self {
parameters: value.parameters.iter().map(Into::into).collect(),
variadic_parameter: value.variadic.as_ref().map(Into::into),
r#return: Some(tfplugin6::function::Return {
r#type: value.return_type.to_string().into_bytes(),
}),
summary: value.summary.clone(),
description: value.description.content.clone(),
description_kind: match value.description.kind {
StringKind::Markdown => tfplugin6::StringKind::Markdown,
StringKind::Plain => tfplugin6::StringKind::Plain,
} as i32,
deprecation_message: match &value.deprecated {
Some(msg) if msg.is_empty() => "deprecated".to_owned(),
Some(msg) => msg.clone(),
None => String::new(),
},
}
}
}