#![allow(clippy::match_like_matches_macro)]
use heck::{ToSnakeCase, ToUpperCamelCase};
use proc_macro2::{Ident, Span, TokenStream};
use quote::quote;
use std::cell::{Cell, RefCell};
use std::collections::HashMap;
use crate::types::{
ArgumentDefinition, ConjureDefinition, Documentation, LogSafety, PrimitiveType, Type,
TypeDefinition, TypeName,
};
enum CachedLogSafety {
Uncomputed,
Computed(Option<LogSafety>),
}
struct TypeContext {
def: TypeDefinition,
has_double: Cell<Option<bool>>,
is_copy: Cell<Option<bool>>,
log_safety: RefCell<CachedLogSafety>,
}
pub struct Context {
types: HashMap<TypeName, TypeContext>,
exhaustive: bool,
staged_builders: bool,
strip_prefix: Vec<String>,
version: Option<String>,
}
impl Context {
pub fn new(
defs: &ConjureDefinition,
exhaustive: bool,
staged_builders: bool,
strip_prefix: Option<&str>,
version: Option<&str>,
) -> Context {
let mut context = Context {
types: HashMap::new(),
exhaustive,
staged_builders,
strip_prefix: vec![],
version: version.map(str::to_owned),
};
if let Some(strip_prefix) = strip_prefix {
context.strip_prefix = context.raw_module_path(strip_prefix);
}
for def in defs.types() {
let name = match &def {
TypeDefinition::Alias(def) => def.type_name().clone(),
TypeDefinition::Enum(def) => def.type_name().clone(),
TypeDefinition::Object(def) => def.type_name().clone(),
TypeDefinition::Union(def) => def.type_name().clone(),
};
context.types.insert(
name,
TypeContext {
def: def.clone(),
has_double: Cell::new(None),
is_copy: Cell::new(None),
log_safety: RefCell::new(CachedLogSafety::Uncomputed),
},
);
}
context
}
pub fn exhaustive(&self) -> bool {
self.exhaustive
}
pub fn staged_builders(&self) -> bool {
self.staged_builders
}
fn needs_box(&self, def: &Type) -> bool {
match def {
Type::Primitive(_) => false,
Type::Optional(def) => self.needs_box(def.item_type()),
Type::List(_) | Type::Set(_) | Type::Map(_) => false,
Type::Reference(def) => self.ref_needs_box(def),
Type::External(def) => self.needs_box(def.fallback()),
}
}
fn ref_needs_box(&self, name: &TypeName) -> bool {
let ctx = &self.types[name];
match &ctx.def {
TypeDefinition::Alias(def) => self.needs_box(def.alias()),
TypeDefinition::Enum(_) => false,
TypeDefinition::Object(_) | TypeDefinition::Union(_) => true,
}
}
pub fn has_double(&self, def: &Type) -> bool {
match def {
Type::Primitive(def) => match *def {
PrimitiveType::Double => true,
_ => false,
},
Type::Optional(def) => self.has_double(def.item_type()),
Type::List(def) => self.has_double(def.item_type()),
Type::Set(def) => self.has_double(def.item_type()),
Type::Map(def) => self.has_double(def.key_type()) || self.has_double(def.value_type()),
Type::Reference(def) => self.ref_has_double(def),
Type::External(def) => self.has_double(def.fallback()),
}
}
fn ref_has_double(&self, name: &TypeName) -> bool {
let ctx = &self.types[name];
if let Some(has_double) = ctx.has_double.get() {
return has_double;
}
ctx.has_double.set(Some(false)); let has_double = match &ctx.def {
TypeDefinition::Alias(def) => self.has_double(def.alias()),
TypeDefinition::Enum(_) => false,
TypeDefinition::Object(def) => def.fields().iter().any(|f| self.has_double(f.type_())),
TypeDefinition::Union(def) => def.union_().iter().any(|f| self.has_double(f.type_())),
};
ctx.has_double.set(Some(has_double));
has_double
}
pub fn is_copy(&self, def: &Type) -> bool {
match def {
Type::Primitive(def) => match *def {
PrimitiveType::String
| PrimitiveType::Binary
| PrimitiveType::Any
| PrimitiveType::Rid
| PrimitiveType::Bearertoken => false,
PrimitiveType::Datetime
| PrimitiveType::Integer
| PrimitiveType::Double
| PrimitiveType::Safelong
| PrimitiveType::Boolean
| PrimitiveType::Uuid => true,
},
Type::Optional(def) => self.is_copy(def.item_type()),
Type::List(_) | Type::Set(_) | Type::Map(_) => false,
Type::Reference(def) => self.ref_is_copy(def),
Type::External(def) => self.is_copy(def.fallback()),
}
}
fn ref_is_copy(&self, name: &TypeName) -> bool {
let ctx = &self.types[name];
if let Some(is_copy) = ctx.is_copy.get() {
return is_copy;
}
let is_copy = match &ctx.def {
TypeDefinition::Alias(def) => self.is_copy(def.alias()),
TypeDefinition::Enum(_) | TypeDefinition::Object(_) | TypeDefinition::Union(_) => false,
};
ctx.is_copy.set(Some(is_copy));
is_copy
}
pub fn is_required(&self, def: &Type) -> bool {
match def {
Type::Primitive(_) => true,
Type::Optional(_) | Type::List(_) | Type::Set(_) | Type::Map(_) => false,
Type::Reference(def) => self.ref_is_required(def),
Type::External(def) => self.is_required(def.fallback()),
}
}
fn ref_is_required(&self, name: &TypeName) -> bool {
let ctx = &self.types[name];
match &ctx.def {
TypeDefinition::Alias(def) => self.is_required(def.alias()),
TypeDefinition::Enum(_) | TypeDefinition::Object(_) | TypeDefinition::Union(_) => true,
}
}
pub fn is_default(&self, def: &Type) -> bool {
match def {
Type::Primitive(def) => match *def {
PrimitiveType::String
| PrimitiveType::Integer
| PrimitiveType::Double
| PrimitiveType::Safelong
| PrimitiveType::Binary
| PrimitiveType::Boolean => true,
PrimitiveType::Datetime
| PrimitiveType::Any
| PrimitiveType::Uuid
| PrimitiveType::Rid
| PrimitiveType::Bearertoken => false,
},
Type::Optional(_) | Type::List(_) | Type::Set(_) | Type::Map(_) => true,
Type::Reference(def) => self.ref_is_default(def),
Type::External(def) => self.is_default(def.fallback()),
}
}
fn ref_is_default(&self, name: &TypeName) -> bool {
let ctx = &self.types[name];
match &ctx.def {
TypeDefinition::Alias(def) => self.is_default(def.alias()),
TypeDefinition::Enum(_) | TypeDefinition::Object(_) | TypeDefinition::Union(_) => false,
}
}
pub fn is_display(&self, def: &Type) -> bool {
match def {
Type::Primitive(def) => match *def {
PrimitiveType::String
| PrimitiveType::Datetime
| PrimitiveType::Integer
| PrimitiveType::Double
| PrimitiveType::Safelong
| PrimitiveType::Boolean
| PrimitiveType::Uuid
| PrimitiveType::Rid => true,
PrimitiveType::Binary | PrimitiveType::Any | PrimitiveType::Bearertoken => false,
},
Type::Optional(_) | Type::List(_) | Type::Set(_) | Type::Map(_) => false,
Type::Reference(def) => self.ref_is_display(def),
Type::External(def) => self.is_display(def.fallback()),
}
}
fn ref_is_display(&self, name: &TypeName) -> bool {
match &self.types[name].def {
TypeDefinition::Alias(def) => self.is_display(def.alias()),
TypeDefinition::Enum(_) => true,
TypeDefinition::Object(_) | TypeDefinition::Union(_) => false,
}
}
pub fn rust_type(&self, this_type: &TypeName, def: &Type) -> TokenStream {
self.rust_type_inner(this_type, def, false)
}
fn rust_type_inner(&self, this_type: &TypeName, def: &Type, key: bool) -> TokenStream {
match def {
Type::Primitive(def) => match *def {
PrimitiveType::String => self.string_ident(this_type),
PrimitiveType::Datetime => quote!(conjure_object::DateTime<conjure_object::Utc>),
PrimitiveType::Integer => quote!(i32),
PrimitiveType::Double => {
if key {
quote!(conjure_object::DoubleKey)
} else {
quote!(f64)
}
}
PrimitiveType::Safelong => quote!(conjure_object::SafeLong),
PrimitiveType::Binary => quote!(conjure_object::ByteBuf),
PrimitiveType::Any => quote!(conjure_object::Any),
PrimitiveType::Boolean => quote!(bool),
PrimitiveType::Uuid => quote!(conjure_object::Uuid),
PrimitiveType::Rid => quote!(conjure_object::ResourceIdentifier),
PrimitiveType::Bearertoken => quote!(conjure_object::BearerToken),
},
Type::Optional(def) => {
let option = self.option_ident(this_type);
let item = self.rust_type_inner(this_type, def.item_type(), key);
quote!(#option<#item>)
}
Type::List(def) => {
let vec = self.vec_ident(this_type);
let item = self.rust_type_inner(this_type, def.item_type(), key);
quote!(#vec<#item>)
}
Type::Set(def) => {
let item = self.rust_type_inner(this_type, def.item_type(), true);
quote!(std::collections::BTreeSet<#item>)
}
Type::Map(def) => {
let key = self.rust_type_inner(this_type, def.key_type(), true);
let value = self.rust_type(this_type, def.value_type());
quote!(std::collections::BTreeMap<#key, #value>)
}
Type::Reference(def) => self.type_path(this_type, def),
Type::External(def) => self.rust_type_inner(this_type, def.fallback(), key),
}
}
pub fn boxed_rust_type(&self, this_type: &TypeName, def: &Type) -> TokenStream {
match def {
Type::Optional(def) => {
let option = self.option_ident(this_type);
let item = self.boxed_rust_type(this_type, def.item_type());
quote!(#option<#item>)
}
Type::Reference(def) => self.ref_boxed_rust_type(this_type, def),
Type::External(def) => self.boxed_rust_type(this_type, def.fallback()),
def => self.rust_type(this_type, def),
}
}
fn ref_boxed_rust_type(&self, this_type: &TypeName, name: &TypeName) -> TokenStream {
let ctx = &self.types[name];
let needs_box = match &ctx.def {
TypeDefinition::Alias(def) => self.needs_box(def.alias()),
TypeDefinition::Enum(_) => false,
TypeDefinition::Object(_) => match &self.types[this_type].def {
TypeDefinition::Union(_) => false,
_ => true,
},
TypeDefinition::Union(_) => true,
};
let unboxed = self.type_path(this_type, name);
if needs_box {
let box_ = self.box_ident(name);
quote!(#box_<#unboxed>)
} else {
unboxed
}
}
#[allow(clippy::only_used_in_recursion)]
pub fn option_inner_type<'a>(&self, def: &'a Type) -> Option<&'a Type> {
match def {
Type::Optional(def) => Some(def.item_type()),
Type::External(def) => self.option_inner_type(def.fallback()),
_ => None,
}
}
pub fn borrowed_rust_type(&self, this_type: &TypeName, def: &Type) -> TokenStream {
match def {
Type::Primitive(def) => match *def {
PrimitiveType::String => quote!(&str),
PrimitiveType::Datetime => quote!(conjure_object::DateTime<conjure_object::Utc>),
PrimitiveType::Integer => quote!(i32),
PrimitiveType::Double => quote!(f64),
PrimitiveType::Safelong => quote!(conjure_object::SafeLong),
PrimitiveType::Binary => quote!(&[u8]),
PrimitiveType::Any => quote!(&conjure_object::Any),
PrimitiveType::Boolean => quote!(bool),
PrimitiveType::Uuid => quote!(conjure_object::Uuid),
PrimitiveType::Rid => quote!(&conjure_object::ResourceIdentifier),
PrimitiveType::Bearertoken => quote!(&conjure_object::BearerToken),
},
Type::Optional(def) => {
let option = self.option_ident(this_type);
let item = self.borrowed_rust_type(this_type, def.item_type());
quote!(#option<#item>)
}
Type::List(def) => {
let item = self.rust_type(this_type, def.item_type());
quote!(&[#item])
}
Type::Set(def) => {
let item = self.rust_type_inner(this_type, def.item_type(), true);
quote!(&std::collections::BTreeSet<#item>)
}
Type::Map(def) => {
let key = self.rust_type_inner(this_type, def.key_type(), true);
let value = self.rust_type(this_type, def.value_type());
quote!(&std::collections::BTreeMap<#key, #value>)
}
Type::Reference(def) => self.borrowed_rust_type_ref(this_type, def),
Type::External(def) => self.borrowed_rust_type(this_type, def.fallback()),
}
}
fn borrowed_rust_type_ref(&self, this_type: &TypeName, name: &TypeName) -> TokenStream {
let ctx = &self.types[name];
let type_ = self.type_path(this_type, name);
match &ctx.def {
TypeDefinition::Alias(def) => {
if self.is_copy(def.alias()) {
type_
} else {
quote!(&#type_)
}
}
TypeDefinition::Enum(_) | TypeDefinition::Object(_) | TypeDefinition::Union(_) => {
quote!(&#type_)
}
}
}
pub fn borrow_rust_type(&self, value: TokenStream, def: &Type) -> TokenStream {
match def {
Type::Primitive(def) => match *def {
PrimitiveType::String => quote!(&*#value),
PrimitiveType::Binary => quote!(&**#value),
PrimitiveType::Any | PrimitiveType::Rid | PrimitiveType::Bearertoken => {
quote!(&#value)
}
PrimitiveType::Datetime
| PrimitiveType::Integer
| PrimitiveType::Double
| PrimitiveType::Safelong
| PrimitiveType::Boolean
| PrimitiveType::Uuid => value,
},
Type::Optional(def) => {
let borrow_item = self.borrow_rust_type(quote!(*o), def.item_type());
quote!(#value.as_ref().map(|o| #borrow_item))
}
Type::List(_) => quote!(&*#value),
Type::Set(_) | Type::Map(_) => quote!(&#value),
Type::Reference(def) => self.borrow_rust_type_ref(value, def),
Type::External(def) => self.borrow_rust_type(value, def.fallback()),
}
}
fn borrow_rust_type_ref(&self, value: TokenStream, name: &TypeName) -> TokenStream {
let ctx = &self.types[name];
match &ctx.def {
TypeDefinition::Alias(def) => {
if self.needs_box(def.alias()) {
quote!(&*#value)
} else if self.is_copy(def.alias()) {
value
} else {
quote!(&#value)
}
}
TypeDefinition::Enum(_) => quote!(&#value),
TypeDefinition::Object(_) | TypeDefinition::Union(_) => quote!(&*#value),
}
}
pub fn setter_bounds(
&self,
this_type: &TypeName,
def: &Type,
value_ident: TokenStream,
) -> SetterBounds {
match def {
Type::Primitive(primitive) => match *primitive {
PrimitiveType::String => {
let into = self.into_ident(this_type);
let string = self.string_ident(this_type);
SetterBounds::Generic {
argument_bound: quote!(#into<#string>),
assign_rhs: quote!(#value_ident.into()),
}
}
PrimitiveType::Binary => {
let into = self.into_ident(this_type);
let vec = self.vec_ident(this_type);
SetterBounds::Generic {
argument_bound: quote!(#into<#vec<u8>>),
assign_rhs: quote!(conjure_object::ByteBuf::from(#value_ident)),
}
}
PrimitiveType::Any => SetterBounds::Generic {
argument_bound: quote!(conjure_object::serde::Serialize),
assign_rhs: quote! {
conjure_object::Any::new(#value_ident).expect("value failed to serialize")
},
},
_ => SetterBounds::Simple {
argument_type: self.rust_type(this_type, def),
assign_rhs: quote!(#value_ident),
},
},
Type::Optional(def) => {
let into = self.into_ident(this_type);
let option = self.option_ident(this_type);
let item_type = self.rust_type(this_type, def.item_type());
let assign_rhs = if self.needs_box(def.item_type()) {
let box_ = self.box_ident(this_type);
quote!(#value_ident.into().map(#box_::new))
} else {
quote!(#value_ident.into())
};
SetterBounds::Generic {
argument_bound: quote!(#into<#option<#item_type>>),
assign_rhs,
}
}
Type::List(def) => {
let into_iterator = self.into_iterator_ident(this_type);
let item_type = self.rust_type(this_type, def.item_type());
SetterBounds::Collection {
argument_bound: quote!(#into_iterator<Item = #item_type>),
type_: CollectionType::List {
value: self.collection_setter_bounds(
this_type,
def.item_type(),
quote!(value),
false,
),
},
}
}
Type::Set(def) => {
let into_iterator = self.into_iterator_ident(this_type);
let item_type = self.rust_type_inner(this_type, def.item_type(), true);
SetterBounds::Collection {
argument_bound: quote!(#into_iterator<Item = #item_type>),
type_: CollectionType::Set {
value: self.collection_setter_bounds(
this_type,
def.item_type(),
quote!(value),
true,
),
},
}
}
Type::Map(def) => {
let into_iterator = self.into_iterator_ident(this_type);
let key_type = self.rust_type_inner(this_type, def.key_type(), true);
let value_type = self.rust_type(this_type, def.value_type());
SetterBounds::Collection {
argument_bound: quote!(#into_iterator<Item = (#key_type, #value_type)>),
type_: CollectionType::Map {
key: self.collection_setter_bounds(
this_type,
def.key_type(),
quote!(key),
true,
),
value: self.collection_setter_bounds(
this_type,
def.value_type(),
quote!(value),
false,
),
},
}
}
Type::Reference(def) => {
let argument_type = self.type_path(this_type, def);
let mut assign_rhs = value_ident;
if self.ref_needs_box(def) {
let box_ = self.box_ident(this_type);
assign_rhs = quote!(#box_::new(#assign_rhs));
}
SetterBounds::Simple {
argument_type,
assign_rhs,
}
}
Type::External(def) => self.setter_bounds(this_type, def.fallback(), value_ident),
}
}
fn collection_setter_bounds(
&self,
this_type: &TypeName,
def: &Type,
value_ident: TokenStream,
key: bool,
) -> CollectionSetterBounds {
match def {
Type::Primitive(primitive) => match *primitive {
PrimitiveType::String => {
let into = self.into_ident(this_type);
let string = self.string_ident(this_type);
CollectionSetterBounds::Generic {
argument_bound: quote!(#into<#string>),
assign_rhs: quote!(#value_ident.into()),
}
}
PrimitiveType::Binary => {
let into = self.into_ident(this_type);
let vec = self.vec_ident(this_type);
CollectionSetterBounds::Generic {
argument_bound: quote!(#into<#vec<u8>>),
assign_rhs: quote!(conjure_object::ByteBuf::from(#value_ident)),
}
}
PrimitiveType::Any => CollectionSetterBounds::Generic {
argument_bound: quote!(conjure_object::serde::Serialize),
assign_rhs: quote! {
conjure_object::Any::new(#value_ident).expect("value failed to serialize")
},
},
_ => CollectionSetterBounds::Simple {
argument_type: self.rust_type_inner(this_type, def, key),
assign_rhs: value_ident,
},
},
Type::Optional(def) => {
let into = self.into_ident(this_type);
let option = self.option_ident(this_type);
let item_type = self.rust_type(this_type, def.item_type());
CollectionSetterBounds::Generic {
argument_bound: quote!(#into<#option<#item_type>>),
assign_rhs: quote!(#value_ident.into()),
}
}
Type::List(def) => {
let into_iterator = self.into_iterator_ident(this_type);
let item_type = self.rust_type_inner(this_type, def.item_type(), key);
CollectionSetterBounds::Generic {
argument_bound: quote!(#into_iterator<Item = #item_type>),
assign_rhs: quote!(#value_ident.into_iter().collect()),
}
}
Type::Set(def) => {
let into_iterator = self.into_iterator_ident(this_type);
let item_type = self.rust_type_inner(this_type, def.item_type(), true);
CollectionSetterBounds::Generic {
argument_bound: quote!(#into_iterator<Item = #item_type>),
assign_rhs: quote!(#value_ident.into_iter().collect()),
}
}
Type::Map(def) => {
let into_iterator = self.into_iterator_ident(this_type);
let key_type = self.rust_type_inner(this_type, def.key_type(), true);
let value_type = self.rust_type(this_type, def.value_type());
CollectionSetterBounds::Generic {
argument_bound: quote!(#into_iterator<Item = (#key_type, #value_type)>),
assign_rhs: quote!(#value_ident.into_iter().collect()),
}
}
Type::Reference(def) => CollectionSetterBounds::Simple {
argument_type: self.type_path(this_type, def),
assign_rhs: value_ident,
},
Type::External(def) => {
self.collection_setter_bounds(this_type, def.fallback(), value_ident, key)
}
}
}
pub fn is_empty_method(&self, def: &Type) -> Option<TokenStream> {
match def {
Type::Primitive(_) => None,
Type::Optional(_) => Some(quote!(is_none)),
Type::List(_) | Type::Set(_) | Type::Map(_) => Some(quote!(is_empty)),
Type::Reference(def) => self.is_empty_method_ref(def),
Type::External(def) => self.is_empty_method(def.fallback()),
}
}
fn is_empty_method_ref(&self, name: &TypeName) -> Option<TokenStream> {
let ctx = &self.types[name];
match &ctx.def {
TypeDefinition::Alias(def) => self.is_empty_method(def.alias()),
TypeDefinition::Enum(_) | TypeDefinition::Object(_) | TypeDefinition::Union(_) => None,
}
}
pub fn is_binary(&self, def: &Type) -> bool {
match def {
Type::Primitive(PrimitiveType::Binary) => true,
Type::Primitive(_)
| Type::Optional(_)
| Type::List(_)
| Type::Set(_)
| Type::Map(_) => false,
Type::Reference(def) => self.is_binary_ref(def),
Type::External(def) => self.is_binary(def.fallback()),
}
}
fn is_binary_ref(&self, name: &TypeName) -> bool {
let ctx = &self.types[name];
match &ctx.def {
TypeDefinition::Alias(def) => self.is_binary(def.alias()),
TypeDefinition::Enum(_) | TypeDefinition::Object(_) | TypeDefinition::Union(_) => false,
}
}
pub fn is_plain(&self, def: &Type) -> bool {
match def {
Type::Primitive(primitive) => match primitive {
PrimitiveType::String
| PrimitiveType::Datetime
| PrimitiveType::Integer
| PrimitiveType::Double
| PrimitiveType::Safelong
| PrimitiveType::Binary
| PrimitiveType::Boolean
| PrimitiveType::Uuid
| PrimitiveType::Rid
| PrimitiveType::Bearertoken => true,
PrimitiveType::Any => false,
},
Type::Optional(_) | Type::List(_) | Type::Set(_) | Type::Map(_) => false,
Type::Reference(def) => self.is_plain_ref(def),
Type::External(def) => self.is_plain(def.fallback()),
}
}
fn is_plain_ref(&self, name: &TypeName) -> bool {
let ctx = &self.types[name];
match &ctx.def {
TypeDefinition::Alias(def) => self.is_plain(def.alias()),
TypeDefinition::Enum(_) => true,
TypeDefinition::Object(_) | TypeDefinition::Union(_) => false,
}
}
pub fn is_iterable(&self, def: &Type) -> bool {
match def {
Type::Primitive(_) => false,
Type::Optional(_) | Type::List(_) | Type::Set(_) | Type::Map(_) => true,
Type::Reference(def) => self.is_iterable_ref(def),
Type::External(def) => self.is_iterable(def.fallback()),
}
}
fn is_iterable_ref(&self, name: &TypeName) -> bool {
let ctx = &self.types[name];
match &ctx.def {
TypeDefinition::Alias(def) => self.is_iterable(def.alias()),
TypeDefinition::Enum(_) | TypeDefinition::Object(_) | TypeDefinition::Union(_) => false,
}
}
pub fn is_optional<'a>(&'a self, def: &'a Type) -> Option<&'a Type> {
match def {
Type::Primitive(_) | Type::List(_) | Type::Set(_) | Type::Map(_) => None,
Type::Optional(def) => Some(def.item_type()),
Type::Reference(def) => self.is_optional_ref(def),
Type::External(def) => self.is_optional(def.fallback()),
}
}
fn is_optional_ref(&self, name: &TypeName) -> Option<&Type> {
let ctx = &self.types[name];
match &ctx.def {
TypeDefinition::Alias(def) => self.is_optional(def.alias()),
TypeDefinition::Enum(_) | TypeDefinition::Object(_) | TypeDefinition::Union(_) => None,
}
}
pub fn is_list(&self, def: &Type) -> bool {
match def {
Type::List(_) => true,
Type::Primitive(_) | Type::Optional(_) | Type::Set(_) | Type::Map(_) => false,
Type::Reference(def) => self.is_list_ref(def),
Type::External(def) => self.is_list(def.fallback()),
}
}
fn is_list_ref(&self, name: &TypeName) -> bool {
let ctx = &self.types[name];
match &ctx.def {
TypeDefinition::Alias(def) => self.is_list(def.alias()),
TypeDefinition::Enum(_) | TypeDefinition::Object(_) | TypeDefinition::Union(_) => false,
}
}
pub fn is_set(&self, def: &Type) -> bool {
match def {
Type::Set(_) => true,
Type::Primitive(_) | Type::Optional(_) | Type::List(_) | Type::Map(_) => false,
Type::Reference(def) => self.is_set_ref(def),
Type::External(def) => self.is_set(def.fallback()),
}
}
fn is_set_ref(&self, name: &TypeName) -> bool {
let ctx = &self.types[name];
match &ctx.def {
TypeDefinition::Alias(def) => self.is_set(def.alias()),
TypeDefinition::Enum(_) | TypeDefinition::Object(_) | TypeDefinition::Union(_) => false,
}
}
#[allow(clippy::only_used_in_recursion)]
pub fn is_double(&self, def: &Type) -> bool {
match def {
Type::Primitive(PrimitiveType::Double) => true,
Type::Optional(def) => self.is_double(def.item_type()),
Type::List(def) => self.is_double(def.item_type()),
Type::Primitive(_) | Type::Set(_) | Type::Map(_) | Type::Reference(_) => false,
Type::External(def) => self.is_double(def.fallback()),
}
}
pub fn docs(&self, docs: Option<&Documentation>) -> TokenStream {
match docs {
Some(docs) => {
let docs = docs.lines();
quote!(#(#[doc = #docs])*)
}
None => quote!(),
}
}
pub fn deprecated(&self, deprecated: Option<&Documentation>) -> TokenStream {
match deprecated {
Some(docs) => {
let docs = &**docs;
quote!(#[deprecated(note = #docs)])
}
None => quote!(),
}
}
pub fn allow_deprecated(&self, deprecated: Option<&Documentation>) -> TokenStream {
match deprecated {
Some(_) => quote!(#[allow(deprecated)]),
None => quote!(),
}
}
pub fn box_ident(&self, name: &TypeName) -> TokenStream {
self.prelude_ident(name, "Box", "std::boxed::Box")
}
pub fn result_ident(&self, name: &TypeName) -> TokenStream {
self.prelude_ident(name, "Result", "std::result::Result")
}
pub fn ok_ident(&self, name: &TypeName) -> TokenStream {
self.prelude_ident(name, "Ok", "Result::Ok")
}
pub fn err_ident(&self, name: &TypeName) -> TokenStream {
self.prelude_ident(name, "Err", "Result::Err")
}
pub fn option_ident(&self, name: &TypeName) -> TokenStream {
self.prelude_ident(name, "Option", "std::option::Option")
}
pub fn some_ident(&self, name: &TypeName) -> TokenStream {
self.prelude_ident(name, "Some", "Option::Some")
}
pub fn none_ident(&self, name: &TypeName) -> TokenStream {
self.prelude_ident(name, "None", "Option::None")
}
pub fn string_ident(&self, name: &TypeName) -> TokenStream {
self.prelude_ident(name, "String", "std::string::String")
}
pub fn vec_ident(&self, name: &TypeName) -> TokenStream {
self.prelude_ident(name, "Vec", "std::vec::Vec")
}
#[allow(clippy::wrong_self_convention)]
pub fn from_ident(&self, name: &TypeName) -> TokenStream {
self.prelude_ident(name, "From", "std::convert::From")
}
#[allow(clippy::wrong_self_convention)]
pub fn into_ident(&self, name: &TypeName) -> TokenStream {
self.prelude_ident(name, "Into", "std::convert::Into")
}
pub fn iterator_ident(&self, name: &TypeName) -> TokenStream {
self.prelude_ident(name, "Iterator", "std::iterator::Iterator")
}
#[allow(clippy::wrong_self_convention)]
pub fn into_iterator_ident(&self, name: &TypeName) -> TokenStream {
self.prelude_ident(name, "IntoIterator", "std::iter::IntoIterator")
}
pub fn default_ident(&self, name: &TypeName) -> TokenStream {
self.prelude_ident(name, "Default", "std::default::Default")
}
pub fn sync_ident(&self, name: &TypeName) -> TokenStream {
self.prelude_ident(name, "Sync", "std::marker::Sync")
}
pub fn send_ident(&self, name: &TypeName) -> TokenStream {
self.prelude_ident(name, "Send", "std::marker::Send")
}
fn prelude_ident(&self, name: &TypeName, short: &str, long: &str) -> TokenStream {
let s = if self.type_name(name.name()) == short {
long
} else {
short
};
s.parse().unwrap()
}
pub fn module_name(&self, name: &TypeName) -> String {
self.ident_name(name.name())
}
pub fn field_name(&self, s: &str) -> Ident {
Ident::new(&self.ident_name(s), Span::call_site())
}
fn ident_name(&self, s: &str) -> String {
let mut s = s.to_snake_case();
let keyword = match &*s {
"as" | "break" | "const" | "continue" | "crate" | "else" | "enum" | "extern"
| "false" | "fn" | "for" | "if" | "impl" | "in" | "let" | "loop" | "match" | "mod"
| "move" | "mut" | "pub" | "ref" | "return" | "self" | "static" | "struct"
| "super" | "trait" | "true" | "type" | "unsafe" | "use" | "where" | "while" => true,
"abstract" | "become" | "box" | "do" | "final" | "macro" | "override" | "priv"
| "typeof" | "unsized" | "virtual" | "yield" => true,
"union" | "dyn" => true,
_ => false,
};
if keyword {
s.push('_');
}
s
}
pub fn type_name(&self, name: &str) -> Ident {
let mut name = name.to_upper_camel_case();
let keyword = match &*name {
"Self" => true,
_ => false,
};
if keyword {
name.push('_');
}
Ident::new(&name, Span::call_site())
}
pub fn module_path(&self, name: &TypeName) -> Vec<String> {
let raw = self.raw_module_path(name.package());
if raw.starts_with(&self.strip_prefix) {
raw[self.strip_prefix.len()..].to_vec()
} else {
raw
}
}
fn raw_module_path(&self, package: &str) -> Vec<String> {
package.split('.').map(|s| self.ident_name(s)).collect()
}
fn type_path(&self, this_type: &TypeName, other_type: &TypeName) -> TokenStream {
let this_module_path = self.module_path(this_type);
let other_module_path = self.module_path(other_type);
let shared_prefix = this_module_path
.iter()
.zip(&other_module_path)
.take_while(|(a, b)| a == b)
.count();
let mut components = vec![quote!(super)];
for _ in 0..this_module_path.len() - shared_prefix {
components.push(quote!(super));
}
for component in &other_module_path[shared_prefix..] {
components.push(component.parse().unwrap());
}
let other_type_name = self.type_name(other_type.name());
quote!(#(#components::)* #other_type_name)
}
pub fn is_safe_arg(&self, arg: &ArgumentDefinition) -> bool {
if let Some(log_safety) = arg.safety() {
return *log_safety == LogSafety::Safe;
}
if self.is_legacy_safe_arg(arg) {
return true;
}
self.type_log_safety(arg.type_()) == Some(LogSafety::Safe)
}
fn is_legacy_safe_arg(&self, arg: &ArgumentDefinition) -> bool {
arg.tags().iter().any(|s| s == "safe")
|| arg.markers().iter().any(|a| self.is_legacy_safe_marker(a))
}
fn is_legacy_safe_marker(&self, ty: &Type) -> bool {
match ty {
Type::External(def) => {
let name = def.external_reference();
name.package() == "com.palantir.logsafe" && name.name() == "Safe"
}
_ => false,
}
}
fn type_log_safety(&self, ty: &Type) -> Option<LogSafety> {
match ty {
Type::Primitive(primitive) => self.primitive_log_safety(primitive),
Type::Optional(optional) => self.type_log_safety(optional.item_type()),
Type::List(list) => self.type_log_safety(list.item_type()),
Type::Set(set) => self.type_log_safety(set.item_type()),
Type::Map(map) => self.combine_safety(
self.type_log_safety(map.key_type()),
self.type_log_safety(map.value_type()),
),
Type::Reference(def) => self.type_log_safety_ref(def),
Type::External(_) => None,
}
}
fn primitive_log_safety(&self, primitive: &PrimitiveType) -> Option<LogSafety> {
match primitive {
PrimitiveType::Bearertoken => Some(LogSafety::DoNotLog),
_ => None,
}
}
fn type_log_safety_ref(&self, name: &TypeName) -> Option<LogSafety> {
let ctx = &self.types[name];
if let CachedLogSafety::Computed(safety) = &*ctx.log_safety.borrow() {
return safety.clone();
}
*ctx.log_safety.borrow_mut() = CachedLogSafety::Computed(Some(LogSafety::Safe));
let safety = match &ctx.def {
TypeDefinition::Alias(alias) => alias
.safety()
.cloned()
.or_else(|| self.type_log_safety(alias.alias())),
TypeDefinition::Enum(_) => Some(LogSafety::Safe),
TypeDefinition::Object(object) => object
.fields()
.iter()
.map(|f| {
f.safety()
.cloned()
.or_else(|| self.type_log_safety(f.type_()))
})
.fold(Some(LogSafety::Safe), |a, b| self.combine_safety(a, b)),
TypeDefinition::Union(union_) => union_
.union_()
.iter()
.map(|f| {
f.safety()
.cloned()
.or_else(|| self.type_log_safety(f.type_()))
})
.fold(None, |a, b| self.combine_safety(a, b)),
};
*ctx.log_safety.borrow_mut() = CachedLogSafety::Computed(safety.clone());
safety
}
fn combine_safety(&self, a: Option<LogSafety>, b: Option<LogSafety>) -> Option<LogSafety> {
match (a, b) {
(Some(LogSafety::DoNotLog), _) | (_, Some(LogSafety::DoNotLog)) => {
Some(LogSafety::DoNotLog)
}
(Some(LogSafety::Unsafe), _) | (_, Some(LogSafety::Unsafe)) => Some(LogSafety::Unsafe),
(Some(LogSafety::Safe), Some(LogSafety::Safe)) => Some(LogSafety::Safe),
(Some(LogSafety::Safe), None) | (None, Some(LogSafety::Safe)) | (None, None) => None,
}
}
pub fn version(&self) -> Option<&str> {
self.version.as_deref()
}
}
pub enum SetterBounds {
Simple {
argument_type: TokenStream,
assign_rhs: TokenStream,
},
Generic {
argument_bound: TokenStream,
assign_rhs: TokenStream,
},
Collection {
argument_bound: TokenStream,
type_: CollectionType,
},
}
pub enum CollectionType {
List {
value: CollectionSetterBounds,
},
Set {
value: CollectionSetterBounds,
},
Map {
key: CollectionSetterBounds,
value: CollectionSetterBounds,
},
}
pub enum CollectionSetterBounds {
Simple {
argument_type: TokenStream,
assign_rhs: TokenStream,
},
Generic {
argument_bound: TokenStream,
assign_rhs: TokenStream,
},
}