1use serde::Deserializer;
4use serde::Serializer;
5use std::borrow::Borrow;
6use std::ffi::OsStr;
7use std::fmt::Debug;
8use std::fmt::Display;
9use std::fmt::Formatter;
10use std::hash::Hash;
11use std::ops::Deref;
12use std::sync::Arc;
13use url::Url;
14use v8::NewStringType;
15
16use crate::ToV8;
17
18static EMPTY_STRING: v8::OneByteConst =
19 v8::String::create_external_onebyte_const("".as_bytes());
20
21#[derive(Clone, Copy)]
24#[repr(transparent)]
25pub struct FastStaticString {
26 s: &'static v8::OneByteConst,
27}
28
29impl FastStaticString {
30 pub const fn new(s: &'static v8::OneByteConst) -> Self {
31 FastStaticString { s }
32 }
33
34 pub fn as_str(&self) -> &'static str {
35 self.s.as_ref()
36 }
37
38 pub fn as_bytes(&self) -> &'static [u8] {
39 self.s.as_ref()
40 }
41
42 #[doc(hidden)]
43 pub const fn create_external_onebyte_const(
44 s: &'static [u8],
45 ) -> v8::OneByteConst {
46 v8::String::create_external_onebyte_const(s)
47 }
48
49 pub fn v8_string<'s, 'i>(
50 &self,
51 scope: &mut v8::PinScope<'s, 'i>,
52 ) -> Result<v8::Local<'s, v8::String>, FastStringV8AllocationError> {
53 FastString::from(*self).v8_string(scope)
54 }
55
56 pub const fn into_v8_const_ptr(&self) -> *const v8::OneByteConst {
57 self.s as _
58 }
59}
60
61impl From<&'static v8::OneByteConst> for FastStaticString {
62 fn from(s: &'static v8::OneByteConst) -> Self {
63 Self::new(s)
64 }
65}
66
67impl From<FastStaticString> for *const v8::OneByteConst {
68 fn from(val: FastStaticString) -> Self {
69 val.into_v8_const_ptr()
70 }
71}
72
73impl Hash for FastStaticString {
74 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
75 self.as_str().hash(state)
76 }
77}
78
79impl AsRef<str> for FastStaticString {
80 fn as_ref(&self) -> &str {
81 self.as_str()
82 }
83}
84
85impl Deref for FastStaticString {
86 type Target = str;
87 fn deref(&self) -> &Self::Target {
88 self.as_str()
89 }
90}
91
92impl Borrow<str> for FastStaticString {
93 fn borrow(&self) -> &str {
94 self.as_str()
95 }
96}
97
98impl Debug for FastStaticString {
99 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
100 Debug::fmt(self.as_str(), f)
101 }
102}
103
104impl Default for FastStaticString {
105 fn default() -> Self {
106 FastStaticString { s: &EMPTY_STRING }
107 }
108}
109
110impl PartialEq for FastStaticString {
111 fn eq(&self, other: &Self) -> bool {
112 self.as_bytes() == other.as_bytes()
113 }
114}
115
116impl Eq for FastStaticString {}
117
118impl Display for FastStaticString {
119 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
120 f.write_str(self.as_str())
121 }
122}
123
124#[derive(Debug, deno_error::JsError)]
125#[class(type)]
126pub struct FastStringV8AllocationError;
127
128impl std::error::Error for FastStringV8AllocationError {}
129impl std::fmt::Display for FastStringV8AllocationError {
130 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
131 write!(
132 f,
133 "failed to allocate string; buffer exceeds maximum length"
134 )
135 }
136}
137
138pub struct FastString {
154 inner: FastStringInner,
155}
156
157enum FastStringInner {
158 Static(&'static str),
160
161 StaticAscii(&'static str),
163
164 StaticConst(FastStaticString),
166
167 Owned(Box<str>),
170
171 Arc(Arc<str>),
173}
174
175impl FastString {
176 pub const fn from_static(s: &'static str) -> Self {
180 if s.is_ascii() {
181 Self {
182 inner: FastStringInner::StaticAscii(s),
183 }
184 } else {
185 Self {
186 inner: FastStringInner::Static(s),
187 }
188 }
189 }
190
191 pub unsafe fn from_ascii_static_unchecked(s: &'static str) -> Self {
207 debug_assert!(
208 s.is_ascii(),
209 "use `from_non_ascii_static_unsafe` for non-ASCII strings",
210 );
211 Self {
212 inner: FastStringInner::StaticAscii(s),
213 }
214 }
215
216 pub fn from_non_ascii_static(s: &'static str) -> Self {
227 Self {
228 inner: FastStringInner::Static(s),
229 }
230 }
231
232 pub fn as_static_str(&self) -> Option<&'static str> {
234 match self.inner {
235 FastStringInner::Static(s) => Some(s),
236 FastStringInner::StaticAscii(s) => Some(s),
237 FastStringInner::StaticConst(s) => Some(s.as_str()),
238 _ => None,
239 }
240 }
241
242 pub fn into_cheap_copy(self) -> (Self, Self) {
246 match self.inner {
247 FastStringInner::Owned(s) => {
248 let s: Arc<str> = s.into();
249 (
250 Self {
251 inner: FastStringInner::Arc(s.clone()),
252 },
253 Self {
254 inner: FastStringInner::Arc(s),
255 },
256 )
257 }
258 _ => (self.try_clone().unwrap(), self),
259 }
260 }
261
262 pub fn try_clone(&self) -> Option<Self> {
264 match &self.inner {
265 FastStringInner::Static(s) => Some(Self {
266 inner: FastStringInner::Static(s),
267 }),
268 FastStringInner::StaticAscii(s) => Some(Self {
269 inner: FastStringInner::StaticAscii(s),
270 }),
271 FastStringInner::StaticConst(s) => Some(Self {
272 inner: FastStringInner::StaticConst(*s),
273 }),
274 FastStringInner::Arc(s) => Some(Self {
275 inner: FastStringInner::Arc(s.clone()),
276 }),
277 FastStringInner::Owned(_s) => None,
278 }
279 }
280
281 #[inline(always)]
282 pub fn as_bytes(&self) -> &[u8] {
283 self.as_str().as_bytes()
284 }
285
286 #[inline(always)]
287 pub fn as_str(&self) -> &str {
288 match &self.inner {
289 FastStringInner::Arc(s) => s,
291 FastStringInner::Owned(s) => s,
292 FastStringInner::Static(s) => s,
293 FastStringInner::StaticAscii(s) => s,
294 FastStringInner::StaticConst(s) => s.as_str(),
295 }
296 }
297
298 pub fn v8_string<'a, 'i>(
301 &self,
302 scope: &mut v8::PinScope<'a, 'i>,
303 ) -> Result<v8::Local<'a, v8::String>, FastStringV8AllocationError> {
304 match self.inner {
305 FastStringInner::StaticAscii(s) => {
306 v8::String::new_external_onebyte_static(scope, s.as_bytes())
307 .ok_or(FastStringV8AllocationError)
308 }
309 FastStringInner::StaticConst(s) => {
310 v8::String::new_from_onebyte_const(scope, s.s)
311 .ok_or(FastStringV8AllocationError)
312 }
313 _ => {
314 v8::String::new_from_utf8(scope, self.as_bytes(), NewStringType::Normal)
315 .ok_or(FastStringV8AllocationError)
316 }
317 }
318 }
319
320 pub fn truncate(&mut self, index: usize) {
322 match &mut self.inner {
323 FastStringInner::Static(b) => {
324 self.inner = FastStringInner::Static(&b[..index])
325 }
326 FastStringInner::StaticAscii(b) => {
327 self.inner = FastStringInner::StaticAscii(&b[..index])
328 }
329 FastStringInner::StaticConst(b) => {
330 self.inner = FastStringInner::StaticAscii(&b.as_str()[..index])
331 }
332 FastStringInner::Owned(b) => {
334 self.inner = FastStringInner::Owned(b[..index].to_owned().into())
335 }
336 FastStringInner::Arc(s) => {
338 self.inner = FastStringInner::Arc(s[..index].to_owned().into())
339 }
340 }
341 }
342}
343
344impl Hash for FastString {
345 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
346 self.as_str().hash(state)
347 }
348}
349
350impl AsRef<str> for FastString {
351 fn as_ref(&self) -> &str {
352 self.as_str()
353 }
354}
355
356impl AsRef<[u8]> for FastString {
357 fn as_ref(&self) -> &[u8] {
358 self.as_str().as_ref()
359 }
360}
361
362impl AsRef<OsStr> for FastString {
363 fn as_ref(&self) -> &OsStr {
364 self.as_str().as_ref()
365 }
366}
367
368impl Deref for FastString {
369 type Target = str;
370 fn deref(&self) -> &Self::Target {
371 self.as_str()
372 }
373}
374
375impl Borrow<str> for FastString {
376 fn borrow(&self) -> &str {
377 self.as_str()
378 }
379}
380
381impl Debug for FastString {
382 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
383 Debug::fmt(self.as_str(), f)
384 }
385}
386
387impl Default for FastString {
388 fn default() -> Self {
389 Self {
390 inner: FastStringInner::StaticConst(FastStaticString::default()),
391 }
392 }
393}
394
395impl PartialEq for FastString {
396 fn eq(&self, other: &Self) -> bool {
397 self.as_bytes() == other.as_bytes()
398 }
399}
400
401impl Eq for FastString {}
402
403impl Display for FastString {
404 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
405 f.write_str(self.as_str())
406 }
407}
408
409impl From<FastStaticString> for FastString {
412 fn from(value: FastStaticString) -> Self {
413 Self {
414 inner: FastStringInner::StaticConst(value),
415 }
416 }
417}
418
419impl From<Url> for FastString {
422 fn from(value: Url) -> Self {
423 let s: String = value.into();
424 s.into()
425 }
426}
427
428impl From<String> for FastString {
431 fn from(value: String) -> Self {
432 Self {
433 inner: FastStringInner::Owned(value.into_boxed_str()),
434 }
435 }
436}
437
438impl From<Arc<str>> for FastString {
441 fn from(value: Arc<str>) -> Self {
442 Self {
443 inner: FastStringInner::Arc(value),
444 }
445 }
446}
447
448impl From<FastString> for Arc<str> {
449 fn from(value: FastString) -> Self {
450 use FastStringInner::*;
451 match value.inner {
452 Static(text) | StaticAscii(text) => text.into(),
453 StaticConst(text) => text.as_ref().into(),
454 Owned(text) => text.into(),
455 Arc(text) => text,
456 }
457 }
458}
459
460impl<'s> ToV8<'s> for FastString {
461 type Error = FastStringV8AllocationError;
462
463 #[inline]
464 fn to_v8<'i>(
465 self,
466 scope: &mut v8::PinScope<'s, 'i>,
467 ) -> Result<v8::Local<'s, v8::Value>, Self::Error> {
468 Ok(self.v8_string(scope)?.into())
469 }
470}
471
472impl serde::Serialize for FastString {
473 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
474 where
475 S: Serializer,
476 {
477 serializer.serialize_str(self.as_str())
478 }
479}
480
481type DeserializeProxy<'de> = &'de str;
482
483impl<'de> serde::Deserialize<'de> for FastString {
484 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
485 where
486 D: Deserializer<'de>,
487 {
488 DeserializeProxy::<'de>::deserialize(deserializer)
489 .map(|v| v.to_owned().into())
490 }
491}
492
493#[macro_export]
498macro_rules! ascii_str_include {
499 ($file:expr_2021) => {{
500 const STR: $crate::v8::OneByteConst =
501 $crate::FastStaticString::create_external_onebyte_const(
502 ::std::include_str!($file).as_bytes(),
503 );
504 let s: &'static $crate::v8::OneByteConst = &STR;
505 $crate::FastStaticString::new(s)
506 }};
507}
508
509#[macro_export]
514macro_rules! ascii_str {
515 ($str:expr_2021) => {{
516 const C: $crate::v8::OneByteConst =
517 $crate::FastStaticString::create_external_onebyte_const($str.as_bytes());
518 $crate::FastStaticString::new(&C)
519 }};
520}
521
522#[macro_export]
524#[doc(hidden)]
525macro_rules! __op_name_fast {
526 ($op:ident) => {{
527 const LITERAL: &'static [u8] = stringify!($op).as_bytes();
528 const STR: $crate::v8::OneByteConst =
529 $crate::FastStaticString::create_external_onebyte_const(LITERAL);
530 let s: &'static $crate::v8::OneByteConst = &STR;
531 (stringify!($op), $crate::FastStaticString::new(s))
532 }};
533}
534
535#[cfg(test)]
536mod tests {
537 use super::*;
538
539 #[test]
540 fn string_eq() {
541 let s: FastString = ascii_str!("Testing").into();
542 assert_eq!("Testing", s.as_str());
543 let s2 = FastString::from_static("Testing");
544 assert_eq!(s, s2);
545 let (s1, s2) = s.into_cheap_copy();
546 assert_eq!("Testing", s1.as_str());
547 assert_eq!("Testing", s2.as_str());
548
549 let s = FastString::from("Testing".to_owned());
550 assert_eq!("Testing", s.as_str());
551 let (s1, s2) = s.into_cheap_copy();
552 assert_eq!("Testing", s1.as_str());
553 assert_eq!("Testing", s2.as_str());
554 }
555
556 #[test]
557 fn truncate() {
558 let mut s = "123456".to_owned();
559 s.truncate(3);
560
561 let mut code: FastString = ascii_str!("123456").into();
562 code.truncate(3);
563 assert_eq!(s, code.as_str());
564
565 let mut code: FastString = "123456".to_owned().into();
566 code.truncate(3);
567 assert_eq!(s, code.as_str());
568
569 let arc_str: Arc<str> = "123456".into();
570 let mut code: FastString = arc_str.into();
571 code.truncate(3);
572 assert_eq!(s, code.as_str());
573 }
574
575 #[test]
576 fn test_large_include() {
577 }
581
582 #[test]
584 fn test_const() {
585 const _: (&str, FastStaticString) = __op_name_fast!(op_name);
586 const _: FastStaticString = ascii_str!("hmm");
587 const _: FastStaticString = ascii_str!(concat!("hmm", "hmmmmm"));
588 const _: FastStaticString = ascii_str_include!("Cargo.toml");
589 const _: FastStaticString = ascii_str_include!(concat!("./", "Cargo.toml"));
590 }
591}