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}