json_str/
lib.rs

1//! Json String Literal Generator
2//!
3//! Write json with Rust syntax instead of hard to read inline strings.
4//! This crate doesn't evaluate any expressions, it just converts a Rust _token tree_ into a
5//! minified json string.
6//!
7//! # Usage
8//!
9//! This crate is on [crates.io](https://crates.io/crates/json_str).
10//!
11//! There are two ways to reference `json_str` in your projects, depending on whether you're on
12//! the `stable`/`beta` or `nightly` channels.
13//!
14//! ## Stable
15//!
16//! To get started, add `json_str` to your `Cargo.toml`:
17//!
18//! ```ignore
19//! [dependencies]
20//! json_str = "*"
21//! ```
22//!
23//! And reference it in your crate root:
24//!
25//! ```ignore
26//! #[macro_use]
27//! extern crate json_str;
28//! ```
29//!
30//! ## Nightly
31//!
32//! To get started, add `json_str` to your `Cargo.toml`:
33//!
34//! ```ignore
35//! [dependencies]
36//! json_str = { version = "*", features = "nightly" }
37//! ```
38//!
39//! And reference it in your crate root:
40//!
41//! ```ignore
42//! #![feature(plugin)]
43//! #![plugin(json_str)]
44//! ```
45//!
46//! If you're on the `nightly` channel, it's better to use the above `plugin` version with the `nightly`
47//! feature because the conversion and sanitisation takes place at compile-time instead of runtime,
48//! saving precious runtime cycles.
49//!
50//! ## Examples
51//!
52//! ### Literals
53//! 
54//! The `json_str!` macro will take an inline token tree and return a sanitised json `String`:
55//!
56//! ```ignore
57//! let json = json_str!({
58//!     "query": {
59//!         "filtered": {
60//!             "query": {
61//!                 "match_all": {}
62//!             },
63//!             "filter": {
64//!                 "geo_distance": {
65//!                     "distance": "20km",
66//!                     "location": {
67//!                         "lat": 37.776,
68//!                         "lon": -122.41
69//!                     }
70//!                 }
71//!             }
72//!         }
73//!     }
74//! });
75//! ```
76//!
77//! This will also work for unquoted keys for something a bit more `rusty`:
78//!
79//! ```ignore
80//! let json = json_str!({
81//!     query: {
82//!         filtered: {
83//!             query: {
84//!                 match_all: {}
85//!             },
86//!             filter: {
87//!                 geo_distance: {
88//!                     distance: "20km",
89//!                     location: {
90//!                         lat: 37.776,
91//!                         lon: -122.41
92//!                     }
93//!                 }
94//!             }
95//!         }
96//!     }
97//! });
98//! ```
99//!
100//! On `nightly`, there's an additional plugin called `json_lit` that returns a `&'static str`
101//! instead of a `String`, so you can avoid allocating each time. The syntax is otherwise the same
102//! as `json_str`:
103//!
104//! ```ignore
105//! let json = json_lit!({
106//!     query: {
107//!         filtered: {
108//!             query: {
109//!                 match_all: {}
110//!             },
111//!             filter: {
112//!                 geo_distance: {
113//!                     distance: "20km",
114//!                     location: {
115//!                         lat: 37.776,
116//!                         lon: -122.41
117//!                     }
118//!                 }
119//!             }
120//!         }
121//!     }
122//! });
123//! ```
124//! 
125//! ### Replacement values
126//! 
127//! The `json_fn` macro will convert a set of replacement tokens and token tree
128//! and returns a lambda function that substitutes them:
129//! 
130//! ```ignore
131//! // Declares an inline Fn(&str, &str, &str) -> String
132//! let f = json_fn!(|dst, lat, lon| {
133//!     query: {
134//!         filtered: {
135//!             query: {
136//!                 match_all: {}
137//!             },
138//!             filter: {
139//!                 geo_distance: {
140//!                     distance: $dst,
141//!                     location: {
142//!                         lat: $lat,
143//!                         lon: $lon
144//!                     }
145//!                 }
146//!             }
147//!         }
148//!     }
149//! });
150//! 
151//! // Call the lambda and return the substituted json
152//! let json = f("\"20km\"", "37.776", "-122.41");
153//! ```
154//! 
155//! All input arguments are a `&str`, and the output is a `String`.
156//! Only simple variable substitution is supported, no repeating or
157//! sanitisation of the replacement values.
158
159#![doc(html_root_url = "http://kodraus.github.io/rustdoc/json_str/")]
160#![cfg_attr(feature = "nightly", crate_type="dylib")]
161#![cfg_attr(feature = "nightly", feature(plugin_registrar, rustc_private, quote, plugin, stmt_expr_attributes))]
162
163/// Raw parsers for sanitising a stream of json.
164pub mod parse;
165
166#[cfg(feature = "nightly")]
167include!("lib.rs.in");
168
169#[cfg_attr(not(feature = "nightly"), macro_export)]
170#[cfg(not(feature = "nightly"))]
171macro_rules! json_str {
172    ($j:tt) => ({
173        let json_raw = stringify!($j);
174        let mut json = String::with_capacity(json_raw.len());
175
176        $crate::parse::parse_literal(json_raw.as_bytes(), &mut json);
177
178        json
179    })
180}
181
182#[cfg_attr(not(feature = "nightly"), macro_export)]
183#[cfg(not(feature = "nightly"))]
184macro_rules! json_fn {
185    (|$($repl:ident),*| $j:tt) => (|$($repl),*| {
186        let repls = {
187            let mut repls = ::std::collections::BTreeMap::<&'static str, &str>::new();
188
189            $(repls.insert(stringify!($repl), $repl);)*
190
191            repls
192        };
193
194        let json_raw = stringify!($j);
195
196        let mut fragments = Vec::new();
197        let mut result = String::new();
198        
199        $crate::parse::parse_fragments(json_raw.as_bytes(), &mut fragments);
200
201        for f in fragments {
202            match f {
203                $crate::parse::JsonFragment::Literal(ref l) => result.push_str(l),
204                $crate::parse::JsonFragment::Repl(ref r) => {
205                    let val = repls
206                        .get(r)
207                        .expect(&format!("replacement '{}' is not in the list of fn args", r));
208
209                    result.push_str(val);
210                }
211            }
212        }
213
214        result
215    })
216}