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