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 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230
mod error; mod body; use proc_macro::TokenStream; use quote::{ quote_spanned }; use syn::{ spanned::Spanned }; /// Use this macro to generate serde `Serialize`/`Deserialize` impls in addition /// to an `ApiBody` impl that can hand back information about the shape of the /// type. /// /// # Attributes /// /// Several attributes can be provided to tweak how this works: /// /// - `#[ApiBody`]: Generates `serde` Serialize and Deserialize impls for this type. /// - `#[ApiBody(Serialize,Deserialize`]: The same as above. /// - `#[ApiBody(Serialize)]`: Only generate the `Serialize` impl for this type. /// - `#[ApiBody(Deserialize)]`: Only generate the `Deserialize` impl for this type. /// - `#[api_body(tag = "foo")]`: Used at the top level, right under `#[ApiBody]`, and /// works the same as `#[serde(tag = "foo")]` would. /// - `#[api_body(flatten)]`: Used on a struct field whose value is itself a struct, and /// works the same as `#[serde(flatten)]` would. /// /// # Notes /// /// Unit enums like `enum Foo { A, B, C }` will automatically (de)serialize to one-of the string /// literals "A", "B" or "C", which slightly differs from how Serde normally apply serialization. /// Unit and non-unit variants cannot exist in the same enum for this reason, so that it is /// "obvious" how the thing will be (de)serialized. /// /// # Example /// /// ``` /// # use seamless::ApiBody; /// # use serde_json::json; /// /// /// This text will form part of the description of the type /// #[ApiBody] /// struct Foo { /// /// This is a value /// value: usize, /// bar: Bar /// } /// /// /// A 'Bar'y thing /// #[ApiBody] /// enum Bar { /// A { /// /// Hello! /// hello: String /// }, /// /// B! /// B { /// /// Bye! /// bye: String /// } /// } /// /// // Here's an example of what the JSON output (because it's more concise) of /// // obtaining the type info of `Foo` would look like): /// assert_eq!( /// serde_json::to_value(Foo::api_body_info()).unwrap(), /// json!({ /// "description": "This text will form part of the description of the type", /// "shape": { /// "type": "Object", /// "keys": { /// "value": { /// "description": "This is a value", /// "shape": { "type": "Number" } /// }, /// "bar": { /// "description": "A 'Bar'y thing", /// "shape": { /// "type": "OneOf", /// "values": [ /// { /// "description": "", /// "shape": { /// "type": "Object", /// "keys": { /// "kind": { /// "description": "Variant tag", /// "shape": { "type": "StringLiteral", "literal": "A" } /// }, /// "hello": { /// "description": "Hello!", /// "shape": { "type": "String" } /// } /// } /// } /// }, /// { /// "description": "B!", /// "shape": { /// "type": "Object", /// "keys": { /// "kind": { /// "description": "Variant tag", /// "shape": { "type": "StringLiteral", "literal": "B" } /// }, /// "bye": { /// "description": "Bye!", /// "shape": { "type": "String" } /// } /// } /// } /// } /// ] /// } /// }, /// } /// } /// }) /// ) /// ``` #[allow(non_snake_case)] #[proc_macro_attribute] pub fn ApiBody(attrs: TokenStream, input: TokenStream) -> TokenStream { let item = syn::parse_macro_input!(input as syn::Item); let attrs = body::parse_top_attrs(attrs); let s = match item { syn::Item::Struct(s) => { match body::parse_struct(s, attrs) { Ok(res) => res, Err(e) => e.to_compile_error() } }, syn::Item::Enum(e) => { match body::parse_enum(e, attrs) { Ok(res) => res, Err(e) => e.to_compile_error() } }, _ => { // Not applied to struct or enum! Produce compile error at // position of the non-struct item it's applied to quote_spanned! { item.span() => compile_error!("TypeScript can only be used on structs and enums"); } } }; TokenStream::from(s) } /// Use this macro to generate an `Into<ApiError>` implementation for your custom error /// type. Your custom error type needs to implement `Debug` and `Display` in order to /// derive `ApiError`. `Display` in particular determines what the error message will be. /// You can then use attribtues to set the status code, and decide on whether the /// error message will be `internal`-only or `external`. /// /// If the error is marked as being `internal`, the output from the `Display` impl will be /// set as the `internal_message` on the `ApiError` struct, and by default the `external_message` /// field will be set to `"Internal server error"`. You can set the external message to something /// different by using the `external = "some message"` attribute. /// /// If the error is marked as being `external`, the output from the `Display` impl will be /// set as the `external_message` _and_ `internal_message` on the `ApiError`. /// /// You can also set a status code, otherwise the error will return with a status code set to 500. /// /// # Attributes /// /// Several attributes can be provided to tweak how this works: /// - `#[api_error(internal)]`: At the top of a struct or on an enum variant, this /// denotes that the error message is for internal eyes only, and the external message /// will be set to a sensible default. /// - `#[api_error(external = "Foo")]`: At the top of a struct of enum variant, this /// sets the `external_message` to be "Foo", so that the `Display` impl will be set on /// the `internal_message` field only (similar to `internal`, above). /// - `#[api_error(code = 401)]`: At the top of a struct of enum variant, this /// sets the status code to be returned in the `ApiError` struct. /// /// These attributes can be combined. /// /// # Example /// /// ``` /// # use seamless::ApiError; /// # use std::fmt; /// #[derive(ApiError, Debug)] /// #[api_error(internal, code = 401, external = "Whoops!")] /// struct MyError; /// /// // We could use something like `thiserror` to generate our `Display` impls: /// impl std::fmt::Display for MyError { /// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { /// write!(f, "A thing has gone wrong") /// } /// } /// /// let e: ApiError = MyError.into(); /// assert_eq!( /// e, /// ApiError { /// code: 401, /// internal_message: "A thing has gone wrong".to_owned(), /// external_message: "Whoops!".to_owned(), /// value: None /// } /// ); /// ``` #[proc_macro_derive(ApiError, attributes(api_error))] pub fn derive_error(input: TokenStream) -> TokenStream { let item: syn::Item = syn::parse(input).expect("Item"); let s = match item { syn::Item::Struct(s) => { error::parse_struct(s) }, syn::Item::Enum(e) => { error::parse_enum(e) }, _ => { // Not applied to struct or enum! Produce compile error at // position of the non-struct item it's applied to quote_spanned! { item.span() => compile_error!("ApiError can only be used on structs and enums"); } } }; TokenStream::from(s) }