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