include_js_core/
lib.rs

1#![feature(const_fn_transmute)]
2
3use std::{borrow::Borrow, ops::Deref};
4use std::convert::TryFrom;
5
6pub type JSParseError = boa::syntax::parser::ParseError;
7
8/// Wrapper around `str` that ensures it contains _syntactically_ valid Javascript.
9/// This is the borrowed version of `JSString` so `&JSStr` is to `JSString` what `&str` is to `String`
10#[repr(transparent)]
11pub struct JSStr {
12    data: str,
13}
14
15/// Wrapper around `String` that ensures it contains _syntactically_ valid Javascript.
16/// See docs for `JSStr` for more info.
17pub struct JSString {
18    code: String,
19}
20
21pub trait JSTemplate {
22    fn render_template(&self) -> JSString;
23}
24
25
26impl JSStr {
27    /// Checks if the content of `js` is syntactically valid Javascript before
28    /// coersing it to `&JSStr`
29    /// 
30    /// # Examples
31    ///
32    /// ```rust
33    /// use include_js::JSStr;
34    /// 
35    /// let js_str = JSStr::new("function f() {}");
36    /// assert!(js_str.is_ok());
37    /// ```
38    /// 
39    /// ```rust
40    /// use include_js::JSStr;
41    ///
42    /// let js_str = JSStr::new("#include <vector>");
43    /// assert!(js_str.is_err());
44    /// ```
45    pub fn new(js: &str) -> Result<&Self, JSParseError> {
46        let _ = boa::parse(js, false)?;
47
48        // SAFETY: follows from safety of `new_unchecked` and from the line above
49        Ok(unsafe { JSStr::new_unchecked(js) })
50    }
51
52    /// Coerses `js` directly into a `&JSStr` without checking for validity
53    pub const unsafe fn new_unchecked(js: &str) -> &Self {
54        // SAFETY: JSStr is repr(transparent) and contains `str` so transmuting from &str to &JSStr is safe
55        std::mem::transmute(js)
56    }
57
58    /// Converts the `&JSStr` back into an `&str`, this should be a noop.
59    pub fn as_str(&self) -> &str {
60        &self.data
61    }
62}
63
64impl<'a> TryFrom<&'a str> for &'a JSStr {
65    type Error = JSParseError;
66
67    fn try_from(value: &'a str) -> Result<Self, Self::Error> {
68        JSStr::new(value)
69    }
70}
71
72impl AsRef<JSStr> for JSStr {
73    fn as_ref(&self) -> &JSStr {
74        self
75    }
76}
77
78impl AsRef<str> for JSStr {
79    fn as_ref(&self) -> &str {
80        &self.data
81    }
82}
83
84impl<'a> Into<&'a str> for &'a JSStr {
85    fn into(self) -> &'a str {
86        &self.data
87    }
88}
89
90impl ToOwned for JSStr {
91    type Owned = JSString;
92
93    fn to_owned(&self) -> Self::Owned {
94        // SAFETY: self.as_str() comes from JSStr so it must be valid javascript
95        unsafe {
96            JSString::new_unchecked(self.as_str().to_owned())
97        }
98    }
99}
100
101impl JSString {
102    pub fn new(code: String) -> Result<Self, JSParseError> {
103        let _ = JSStr::new(&code)?;
104        Ok(JSString{ code })
105    }
106
107    pub unsafe fn new_unchecked(code: String) -> Self {
108        JSString{ code }
109    }
110
111    pub fn into_string(self) -> String {
112        self.code
113    }
114}
115
116impl TryFrom<String> for JSString {
117    type Error = JSParseError;
118
119    fn try_from(value: String) -> Result<Self, Self::Error> {
120        JSString::new(value)
121    }
122}
123
124impl Into<String> for JSString {
125    fn into(self) -> String {
126        self.code
127    }
128}
129
130impl Borrow<JSStr> for JSString {
131    fn borrow(&self) -> &JSStr {
132        // SAFETY: we are already in JSString so `code` must be valid javascript
133        unsafe { JSStr::new_unchecked(&self.code) }
134    }
135}
136
137impl AsRef<JSStr> for JSString {
138    fn as_ref(&self) -> &JSStr {
139        self.borrow()
140    }
141}
142
143impl Deref for JSString {
144    type Target = JSStr;
145
146    fn deref(&self) -> &Self::Target {
147        // SAFETY: we are already in JSString so `code` must be valid javascript
148        unsafe { JSStr::new_unchecked(&self.code) }
149    }
150}