ferrijs-std 0.2.3

Node and web standard library for the ferrijs QuickJS runtime: WHATWG Streams, Events, AbortController, Buffer, crypto, fs, os, url, zlib and the capability model they enforce (partly derived from awslabs/llrt, Apache-2.0).
Documentation
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use crate::utils::time;
use rquickjs::{
    atom::PredefinedAtom, class::Trace, function::Opt, ArrayBuffer, Coerced, Ctx, Exception, Result,
    Value,
};

use super::blob::Blob;

#[rquickjs::class]
#[derive(Trace, Clone, rquickjs::JsLifetime)]
pub struct File<'js> {
    blob: Blob<'js>,
    filename: String,
    last_modified: i64,
}

#[rquickjs::methods]
impl<'js> File<'js> {
    #[qjs(constructor)]
    fn new(
        ctx: Ctx<'js>,
        data: Value<'js>,
        filename: Coerced<String>,
        options: Opt<Value<'js>>,
    ) -> Result<Self> {
        let mut last_modified = time::now_millis();

        if let Some(ref opts) = options.0 {
            if opts.is_bool() || opts.is_float() || opts.is_int() || opts.is_string() {
                return Err(Exception::throw_type(&ctx, "Invalid options"));
            }

            if let Some(v) = opts.as_object() {
                if let Some(x) = v.get::<_, Option<Coerced<i64>>>("lastModified")? {
                    last_modified = x.0;
                }
            }
        }

        let blob = Blob::from_parts(ctx, Opt(Some(data)), options)?;

        Ok(Self {
            blob,
            filename: filename.0,
            last_modified,
        })
    }

    #[qjs(get)]
    pub fn size(&self) -> usize {
        self.blob.size()
    }

    #[qjs(get)]
    pub fn name(&self) -> String {
        self.filename.clone()
    }

    #[qjs(get, rename = "type")]
    pub fn mime_type(&self) -> String {
        self.blob.mime_type()
    }

    #[qjs(get, rename = "lastModified")]
    pub fn last_modified(&self) -> i64 {
        self.last_modified
    }

    pub fn slice(
        &self,
        ctx: Ctx<'js>,
        start: Opt<isize>,
        end: Opt<isize>,
        content_type: Opt<Value<'js>>,
    ) -> Result<Blob<'js>> {
        self.blob.slice_blob(&ctx, start.0, end.0, content_type.0)
    }

    pub async fn text(&self) -> String {
        self.blob.text().await
    }

    #[qjs(rename = "arrayBuffer")]
    pub async fn array_buffer(&self, ctx: Ctx<'js>) -> Result<ArrayBuffer<'js>> {
        self.blob.array_buffer(ctx).await
    }

    pub async fn bytes(&self, ctx: Ctx<'js>) -> Result<Value<'js>> {
        self.blob.bytes(ctx).await
    }

    pub fn stream(&self, ctx: Ctx<'js>) -> Result<Value<'js>> {
        self.blob.stream(ctx)
    }

    #[qjs(prop, rename = PredefinedAtom::SymbolToStringTag, configurable)]
    pub fn to_string_tag() -> &'static str {
        stringify!(File)
    }
}

impl<'js> File<'js> {
    pub fn from_bytes(
        ctx: &Ctx<'js>,
        data: Vec<u8>,
        filename: String,
        mime_type: Option<String>,
    ) -> Result<Self> {
        // Local delta: upstream passes the bytes through `into_js`, which
        // makes a JS ARRAY OF NUMBERS — and `new Blob([...])` stringifies
        // every non-BufferSource part, so `File.from_bytes(b"hi")` produced
        // a file whose text was "104105". `Blob::from_bytes` wraps the bytes
        // in an ArrayBuffer, which is what the constructor expects.
        let blob = Blob::from_bytes(ctx, data, mime_type.clone())?;

        Ok(Self {
            blob,
            filename,
            last_modified: time::now_millis(),
        })
    }

    pub fn get_blob(&self) -> Blob<'js> {
        self.blob.clone()
    }

    pub fn set_filename(&mut self, filename: String) {
        self.filename = filename;
    }
}