1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
//! Derive macro for automatically implementing `jaded::FromJava` for Rust types.
//!
//! This is provided as a `FromJava` macro.
//!
//! Most implementations of `FromJava` will be very similar and automatically deriving them makes
//! much more sense than writing almost identical impl blocks for every type.
//!
//! For a 'simple' bean style Java class, eg
//!
//! ```java
//! public class Person implements Serializable {
//! private String firstName;
//! private String familyName;
//! private int age;
//! }
//! ```
//! The rust equivalent can be automatically derived
//! ```
//! # use ::jaded_derive::FromJava;
//! #[derive(FromJava)]
//! struct Person {
//! firstName: String,
//! familyName: String,
//! age: i32,
//! }
//! ```
//!
//! To allow both Java and rust to keep to their respective naming conventions,
//! a 'rename' feature is available that allows camelCase java fields to be
//! read into snake_case rust fields
//!
//! ```
//! # use ::jaded_derive::FromJava;
//! #[derive(FromJava)]
//! #[jaded(rename)]
//! struct Person {
//! first_name: String,
//! family_name: String,
//! age: i32,
//! }
//! ```
//!
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use proc_macro_error::{diagnostic, proc_macro_error, Diagnostic, Level, ResultExt};
use syn::{parse2, DeriveInput};
mod attributes;
mod internal;
mod model;
use model::Reader;
type Result<T> = std::result::Result<T, Diagnostic>;
/// # Derive FromJava for custom Rust types
///
/// By default each field is read from a field of the same name in the serialized
/// Java object. In this simple form, all fields must also implement `FromJava`
/// (similar to `derive(Debug)` requiring fields to implement `Debug`).
///
/// To modify the standard behaviour a `jaded` attribute is available
/// ## On structs
/// ### `#[jaded(rename)]`
/// Convert all fields to camelCase before looking them up in the Java object.
/// This allows a field `first_name` to be read from a Java field `firstName`.
///
/// NB. This requires the `renaming` feature as it requires additional
/// dependencies.
/// ### `#[jaded(class = "com.example.Demo")]`
/// By default, Jaded will try to read from the given fields and use them if
/// they're present without first checking the class of the object. This means
/// a class `Foo` with a single `String name` field will be treated exactly
/// the same as a class `Bar` that also has a single `String name` field.
/// This attribute allows a specific class to be given which will be checked
/// at conversion time.
///
/// ## On Fields
/// ### `#[jaded(field = "fieldName")]`
/// Customise the name of the Java field being read.
/// By default each rust field is read from a Java field of the same name. While
/// this is usually fine, it can be useful in some situations to supply a more
/// appropriate name. This allows code to better match domain language or to
/// avoid keyword clashes (eg type is a valid Java field name but can't be used
/// in Rust).
/// This could also be used to rename fields to match naming conventions if
/// the struct level `rename` attribute is not used.
///
/// ### `#[jaded(extract(method_name))]`
/// Serializable classes in Java can customise the way the builtin Serializer
/// writes their state to the output stream. As there is no standard for how this
/// may look, the custom data is written to an annotations field in the
/// intermediate representation and the field name is not available.
/// This data can be read using an interface similar to Java's `ObjectInputStream`
/// but the specifics must be user implemented and can't be derived.
/// The `extract` attribute allows a user method to be used to read a field's
/// value from the custom data stream.
///
/// The value given to the `extract` attribute must be a valid path to a function
/// with the signature:
/// `(&mut AnnotationIter) -> ConversionResult<T> where T: Into<F>`
/// and `F` is the type of the rust field being read.
///
/// By default, the `AnnotationIter` instance passed to the method is the stream
/// written by the concrete class being read. In rare situations, a parent class
/// may also write their own custom data and these can be read by specifying
/// which class in the hierarchy to use where `0` (the default) is the first class
/// in the hierarchy (top down) to include annotations, and 1 is the first subclass
/// of it to include annotations etc.
/// This is passed after the path to the method:
/// `#[jaded(extract(method_name, 1))]`.
///
/// NB. Fields that read from the annotation stream are all passed the same
/// instance of the `AnnotationIter`. This means that later fields do
/// not have to 'fast-forward' through the previous field's data but does
/// mean that the order fields are declared in is important.
/// NB. Fields read via a custom extract method do not have to implement `FromJava`.
#[proc_macro_derive(FromJava, attributes(jaded))]
#[proc_macro_error]
pub fn from_java(input: TokenStream) -> TokenStream {
check_features();
let input = parse(input.into()).unwrap_or_abort();
let model = Reader::read_model(input).unwrap_or_abort();
model.to_tokens().into()
}
/// Previous versions had a renaming feature to enable bulk camelcass conversion.
/// This is now provided by default so warn if users are still using it.
fn check_features() {
#[cfg(feature = "renaming")]
::proc_macro_error::emit_warning!(
proc_macro2::Span::call_site(),
"Renaming feature has been deprecated. Functionality is available with no features enabled"
);
}
/// Convert the original token stream into a DeriveInput instance.
fn parse(input: TokenStream2) -> Result<DeriveInput> {
parse2::<DeriveInput>(input).map_err(|e| {
diagnostic!(
e.span(),
Level::Error,
"Only structs or enums can have FromJava derived",
)
})
}