use syn::Attribute;
use syn::{DataStruct, Error, Field, Ident, LitStr};
use crate::line_protocol_struct::generator::{
GeneratorField, GeneratorImpl, GeneratorMeasurement, GeneratorMember, GeneratorTag, GeneratorTimestamp,
};
#[derive(PartialEq)]
pub enum StructFieldKind {
Tag,
Field,
Timestamp,
}
pub enum StructFieldGenerator {
Tag(GeneratorTag),
Field(GeneratorField),
Timestamp(GeneratorTimestamp),
}
pub struct StructFieldSettings<'a> {
pub target: &'a Ident,
pub field: &'a Field,
pub index: usize,
pub kind: Option<StructFieldKind>,
pub rename: Option<String>,
}
impl<'a> StructFieldSettings<'a> {
fn new(target: &'a Ident, field: &'a Field, index: usize) -> Result<Option<Self>, Error> {
let mut settings = Self {
target,
field,
index,
kind: None,
rename: None,
};
for attr in &field.attrs {
attr.parse_nested_meta(|meta| {
if meta.path.require_ident()? == "tag" {
settings.set_tag()?;
} else if meta.path.require_ident()? == "field" {
settings.set_field()?;
} else if meta.path.require_ident()? == "timestamp" {
settings.set_timestamp()?;
} else if meta.path.require_ident()? == "rename" {
let value = meta.value()?;
let literal = value.parse::<LitStr>()?;
settings.set_rename(literal.value())?;
} else {
Err(settings.error(&format!("unknown attribute '{}'", meta.path.require_ident()?)))?;
}
Ok(())
})?;
}
if settings.kind.is_none() {
Ok(None)
} else {
Ok(Some(settings))
}
}
fn set_tag(&mut self) -> Result<(), Error> {
let message = match self.kind.replace(StructFieldKind::Tag) {
None => return Ok(()),
Some(StructFieldKind::Tag) => "#[line_protocol(tag)] can only be used once per field",
Some(StructFieldKind::Field) => "#[line_protocol(tag)] cannot be used with [line_protocol(field)]",
Some(StructFieldKind::Timestamp) => "#[line_protocol(tag)] cannot be used with [line_protocol(timestamp)]",
};
Err(self.error(message))
}
fn set_field(&mut self) -> Result<(), Error> {
let message = match self.kind.replace(StructFieldKind::Field) {
None => return Ok(()),
Some(StructFieldKind::Tag) => "#[line_protocol(field)] cannot be used with [line_protocol(tag)]",
Some(StructFieldKind::Field) => "#[line_protocol(field)] can only be used once per field",
Some(StructFieldKind::Timestamp) => {
"#[line_protocol(field)] cannot be used with [line_protocol(timestamp)]"
}
};
Err(self.error(message))
}
fn set_timestamp(&mut self) -> Result<(), Error> {
let message = match self.kind.replace(StructFieldKind::Timestamp) {
None => return Ok(()),
Some(StructFieldKind::Tag) => "#[line_protocol(timestamp)] cannot be used with [line_protocol(tag)]",
Some(StructFieldKind::Field) => "#[line_protocol(timestamp)] cannot be used with [line_protocol(field)]",
Some(StructFieldKind::Timestamp) => "#[line_protocol(timestamp)] can only be used once per field",
};
Err(self.error(message))
}
fn set_rename(&mut self, value: String) -> Result<(), Error> {
let message = match self.rename.replace(value) {
None => return Ok(()),
Some(_) => "#[line_protocol(rename = \"...\")] can only be used once per field",
};
Err(self.error(message))
}
fn error(&self, message: &str) -> Error {
Error::new(self.field.ident.as_ref().unwrap_or(self.target).span(), message)
}
fn generator(&self) -> Result<StructFieldGenerator, Error> {
let ident = self.field.ident.clone().map(|i| i.to_string());
let rename = self.rename.clone();
let member = match &self.field.ident {
Some(ident) => GeneratorMember::Named(ident.to_string()),
None => GeneratorMember::Unnamed(self.index),
};
let kind = match &self.kind {
None => return Err(self.error("internal error")),
Some(kind) => kind,
};
Ok(match (kind, rename, ident) {
(StructFieldKind::Tag, Some(name), _) => StructFieldGenerator::Tag(GeneratorTag { name, member }),
(StructFieldKind::Tag, None, Some(name)) => StructFieldGenerator::Tag(GeneratorTag { name, member }),
(StructFieldKind::Field, Some(name), _) => StructFieldGenerator::Field(GeneratorField { name, member }),
(StructFieldKind::Field, None, Some(name)) => StructFieldGenerator::Field(GeneratorField { name, member }),
(StructFieldKind::Timestamp, None, _) => StructFieldGenerator::Timestamp(GeneratorTimestamp { member }),
(StructFieldKind::Timestamp, Some(_), _) => {
return Err(
self.error("#[line_protocol(timestamp)] cannot be used with #[line_protocol(rename = \"...\")]")
);
}
(_, None, _) => {
return Err(self.error("#[line_protocol(rename = \"...\")] must be used on an unnamed field"));
}
})
}
}
pub struct StructSettings<'a> {
pub target: &'a Ident,
pub fields: Vec<StructFieldSettings<'a>>,
pub measurement: Option<String>,
}
impl<'a> StructSettings<'a> {
pub fn new(target: &'a Ident, data: &'a DataStruct, attrs: &[Attribute]) -> Result<Self, Error> {
let mut settings = Self {
target,
fields: data
.fields
.iter()
.enumerate()
.filter_map(|(i, f)| StructFieldSettings::new(target, f, i).transpose())
.collect::<Result<Vec<_>, _>>()?,
measurement: None,
};
for attr in attrs {
attr.parse_nested_meta(|meta| {
if meta.path.require_ident()? == "measurement" {
let value = meta.value()?;
let literal = value.parse::<LitStr>()?;
settings.set_measurement(literal.value())?;
} else {
Err(settings.error(&format!("unknown attribute '{}'", meta.path.require_ident()?)))?;
}
Ok(())
})?;
}
Ok(settings)
}
fn set_measurement(&mut self, value: String) -> Result<(), Error> {
let message = match self.measurement.replace(value) {
None => return Ok(()),
Some(_) => "#[line_protocol(measurement = \"...\")] can only be used once",
};
Err(self.error(message))
}
fn error(&self, message: &str) -> Error {
Error::new(self.target.span(), message)
}
pub fn generator(&self) -> Result<GeneratorImpl, Error> {
let mut tags = Vec::new();
let mut fields = Vec::new();
let mut timestamp = None;
for field in &self.fields {
match field.generator()? {
StructFieldGenerator::Tag(g) => tags.push(g),
StructFieldGenerator::Field(g) => fields.push(g),
StructFieldGenerator::Timestamp(g) => {
if timestamp.replace(g).is_some() {
return Err(self.error("#[line_protocol(timestamp)] cannot be used on multiple fields"));
}
}
}
}
if fields.is_empty() {
return Err(self.error("#[line_protocol(field)] must be used on at least one field"));
}
let measurement = match &self.measurement {
Some(measurement) => measurement,
None => return Err(self.error("#[line_protocol(measurement = \"...\")] must be defined once")),
};
Ok(GeneratorImpl {
target: self.target.to_string(),
measurement: GeneratorMeasurement {
name: measurement.to_string(),
},
tags,
fields,
timestamp,
})
}
}