Skip to content

Commit

Permalink
file type
Browse files Browse the repository at this point in the history
  • Loading branch information
Natoandro committed Oct 3, 2024
1 parent a06a989 commit 599586e
Show file tree
Hide file tree
Showing 14 changed files with 4,204 additions and 70 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ exclude = [
"tests/runtimes/wasm_reflected/rust",
"tests/runtimes/wasm_wire/rust",
"tests/metagen/typegraphs/sample/rs",
"tests/metagen/typegraphs/sample/rs_upload",
"src/pyrt_wit_wire",
]

Expand Down
1 change: 1 addition & 0 deletions src/metagen/src/client_rs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,7 @@ fn render_node_metas(
Rc::new(node_metas::RsNodeMetasRenderer {
name_mapper,
named_types: named_types.clone(),
input_files: manifest.files.clone(),
}),
);
for &id in &manifest.node_metas {
Expand Down
39 changes: 37 additions & 2 deletions src/metagen/src/client_rs/node_metas.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,15 @@ use std::fmt::Write;
use common::typegraph::*;

use super::utils::normalize_type_title;
use crate::{interlude::*, shared::types::*};
use crate::{
interlude::*,
shared::{files::ObjectPath, types::*},
};

pub struct RsNodeMetasRenderer {
pub name_mapper: Rc<super::NameMapper>,
pub named_types: Rc<std::sync::Mutex<HashSet<u32>>>,
pub input_files: Rc<HashMap<u32, Vec<ObjectPath>>>,
}

impl RsNodeMetasRenderer {
Expand Down Expand Up @@ -91,7 +95,9 @@ pub fn {ty_name}() -> NodeMeta {{
ty_name: &str,
return_node: &str,
argument_fields: Option<IndexMap<String, Rc<str>>>,
input_files: Option<String>,
) -> std::fmt::Result {
eprintln!("function={ty_name}; input_files: {:#?}", input_files);
write!(
dest,
r#"
Expand Down Expand Up @@ -121,6 +127,13 @@ pub fn {ty_name}() -> NodeMeta {{
),"#
)?;
}
if let Some(input_files) = input_files {
write!(
dest,
r#"
input_files: Some(input_file_list(&{input_files})),"#
)?;
}
write!(
dest,
r#"
Expand Down Expand Up @@ -179,7 +192,29 @@ impl RenderType for RsNodeMetasRenderer {
};
let node_name = &base.title;
let ty_name = normalize_type_title(node_name).to_pascal_case();
self.render_for_func(renderer, &ty_name, &return_ty_name, props)?;
let input_files = self
.input_files
.get(&cursor.id)
.map(|files| {
files
.iter()
.map(|path| {
path.0
.iter()
.map(ToString::to_string)
.map(|s| serde_json::to_string(&s).unwrap())
.collect::<Vec<_>>()
})
.map(|path| format!("&[{}]", path.join(", ")))
.collect::<Vec<_>>()
})
.map(|files| (!files.is_empty()).then(|| format!("[{}]", files.join(", "))))
.unwrap_or_default();
// TODO
// let input_files = (!input_files.is_empty())
// .then(|| serde_json::to_string(&input_files))
// .transpose()?;
self.render_for_func(renderer, &ty_name, &return_ty_name, props, input_files)?;
ty_name
}
TypeNode::Object { data, base } => {
Expand Down
6 changes: 5 additions & 1 deletion src/metagen/src/client_rs/static/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ package.version = "0.0.1"
[dependencies]
serde = { version = "1.0.203", features = ["derive"] }
serde_json = "1.0.117"
reqwest = { version = "0.12", features = ["blocking","json"] }
reqwest = { version = "0.12.8", features = ["blocking", "json", "stream", "multipart"] }
mime_guess = "2.0"
futures = "0.3"
tokio-util = { version = "0.7", features = ["compat", "io"] }
derive_more = { version = "1.0", features = ["debug"] }

# The options after here are configured for crates intended to be
# wasm artifacts. Remove them if your usage is different
Expand Down
184 changes: 184 additions & 0 deletions src/metagen/src/client_rs/static/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,30 @@ enum ObjectPathSegment {

struct ObjectPath(Vec<ObjectPathSegment>);

fn input_file_list(files: &'static [&'static [&'static str]]) -> Rc<[ObjectPath]> {
let files: Vec<_> = files
.iter()
.map(|path| {
ObjectPath(
path.iter()
.map(|segment| match *segment {
"?" => ObjectPathSegment::Optional,
"[]" => ObjectPathSegment::List,
_ => {
if !segment.starts_with('.') {
// TODO return Result::Err
unreachable!("input file path segments must start with '.'");
}
ObjectPathSegment::Prop(segment[1..].to_string())
}
})
.collect(),
)
})
.collect();
files.into()
}

enum EffectiveObjectPathSegment {
Optional,
ListItem(usize),
Expand All @@ -214,6 +238,166 @@ enum EffectiveObjectPathSegment {

struct EffectiveObjectPath(Vec<EffectiveObjectPathSegment>);

#[derive(derive_more::Debug)]
enum FileData {
Path(std::path::PathBuf),
Bytes(Vec<u8>),
Reader(#[debug(skip)] Box<dyn std::io::Read + Send + 'static>),
Async(reqwest::Body),
}

#[derive(Debug)]
pub struct File {
data: FileData,
file_name: Option<String>,
mime_type: Option<String>,
}

impl serde::Serialize for File {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::ser::Serializer,
{
Err(serde::ser::Error::custom("`File` can't be serialized"))
}
}

impl<'de> serde::Deserialize<'de> for File {
fn deserialize<D>(_: D) -> Result<File, D::Error>
where
D: serde::de::Deserializer<'de>,
{
Err(serde::de::Error::custom("`File` can't be deserialized"))
}
}

impl File {
pub fn from_path<P: Into<std::path::PathBuf>>(path: P) -> Self {
Self {
data: FileData::Path(path.into()),
file_name: None,
mime_type: None,
}
}

pub fn from_bytes<B: Into<Vec<u8>>>(data: B) -> Self {
Self {
data: FileData::Bytes(data.into()),
file_name: None,
mime_type: None,
}
}

pub fn from_reader<R: std::io::Read + Send + 'static>(reader: R) -> Self {
Self {
data: FileData::Reader(Box::new(reader)),
file_name: None,
mime_type: None,
}
}

pub fn from_async_reader<R: futures::io::AsyncRead + Send + 'static>(reader: R) -> Self {
use tokio_util::compat::FuturesAsyncReadCompatExt as _;
let reader = reader.compat();
Self {
data: FileData::Async(reqwest::Body::wrap_stream(
tokio_util::io::ReaderStream::new(reader),
)),
file_name: None,
mime_type: None,
}
}
}

impl From<Vec<u8>> for File {
fn from(data: Vec<u8>) -> Self {
Self::from_bytes(data)
}
}
impl From<std::path::PathBuf> for File {
fn from(path: std::path::PathBuf) -> Self {
Self::from_path(path)
}
}
impl From<&std::path::Path> for File {
fn from(path: &std::path::Path) -> Self {
Self::from_path(path)
}
}

impl File {
pub fn file_name(mut self, file_name: impl Into<String>) -> Self {
self.file_name = Some(file_name.into());
self
}

pub fn mime_type(mut self, mime_type: impl Into<String>) -> Self {
self.mime_type = Some(mime_type.into());
self
}
}

impl TryFrom<File> for reqwest::blocking::multipart::Part {
type Error = BoxErr;

fn try_from(file: File) -> Result<Self, Self::Error> {
let mut part = match file.data {
FileData::Path(path) => {
let file = std::fs::File::open(path.as_path())?;
let file_size = file.metadata()?.len();
let mut part =
reqwest::blocking::multipart::Part::reader_with_length(file, file_size);
if let Some(name) = path.file_name() {
part = part.file_name(name.to_string_lossy().into_owned());
}
part = part.mime_str(
mime_guess::from_path(&path)
.first_or_octet_stream()
.as_ref(),
)?;
part
}

FileData::Bytes(data) => reqwest::blocking::multipart::Part::bytes(data),

FileData::Reader(reader) => reqwest::blocking::multipart::Part::reader(reader),

FileData::Async(_) => {
return Err("async readers are not supported".into());
}
};

if let Some(file_name) = file.file_name {
part = part.file_name(file_name);
}
if let Some(mime_type) = file.mime_type {
part = part.mime_str(&mime_type)?;
}
Ok(part)
}
}

impl File {
pub async fn into_reqwest_part(self) -> Result<reqwest::multipart::Part, BoxErr> {
let mut part = match self.data {
FileData::Path(path) => reqwest::multipart::Part::file(path).await?,
FileData::Bytes(data) => reqwest::multipart::Part::bytes(data),
FileData::Async(body) => reqwest::multipart::Part::stream(body),
FileData::Reader(_) => {
return Err("sync readers are not supported".into());
}
};

if let Some(file_name) = self.file_name {
part = part.file_name(file_name);
}
if let Some(mime_type) = self.mime_type {
part = part.mime_str(&mime_type)?;
}
Ok(part)
}
}

//
// --- --- Graph node types --- --- //
//
Expand Down
6 changes: 3 additions & 3 deletions src/metagen/src/fdk_rust/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,10 +149,10 @@ impl RenderType for RustTypeRenderer {

TypeNode::File { base, .. } if body_required => {
let ty_name = normalize_type_title(&base.title);
self.render_alias(renderer, &ty_name, "Vec<u8>")?;
self.render_alias(renderer, &ty_name, "super::File")?;
ty_name
}
TypeNode::File { .. } => "Vec<u8>".into(),
TypeNode::File { .. } => "super::File".into(),

TypeNode::Any { base, .. } if body_required => {
let ty_name = normalize_type_title(&base.title);
Expand Down Expand Up @@ -473,7 +473,7 @@ pub type MyStrMaybe = Option<MyStr>;
pub type MyInt = i64;
pub type MyFloat = f64;
pub type MyBool = bool;
pub type MyFile = Vec<u8>;
pub type MyFile = File;
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct MyObj {
#[serde(rename = "myString")]
Expand Down
17 changes: 14 additions & 3 deletions tests/metagen/metagen_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -643,7 +643,7 @@ Meta.test({
}
});

Meta.test({
Meta.test.only({
name: "client table suite for file upload",
}, async (metaTest) => {
const scriptsPath = join(import.meta.dirname!, "typegraphs/sample");
Expand All @@ -656,6 +656,9 @@ Meta.test({
},
...`-C ${scriptsPath} gen`.split(" "),
);
console.log("------>------> STDERR");
console.log(res.stderr);
console.log("------>------> STDERR end");
assertEquals(res.code, 0);

const expectedSchemaU1 = zod.object({
Expand All @@ -664,14 +667,22 @@ Meta.test({
const expectedSchemaUn = zod.object({
uploadMany: zod.boolean(),
});
const expectedSchema = zod.tuple([expectedSchemaU1, expectedSchemaUn]);
const cases = [
{
skip: false,
skip: true,
name: "client_ts_upload",
command: $`bash -c "deno run -A main.ts"`.cwd(
join(scriptsPath, "ts_upload"),
),
expected: zod.tuple([expectedSchemaU1, expectedSchemaUn]),
expected: expectedSchema,
},
{
name: "client_rs_upload",
command: $`cargo run`.cwd(
join(scriptsPath, "rs_upload"),
),
expected: expectedSchema,
},
];

Expand Down
26 changes: 15 additions & 11 deletions tests/metagen/typegraphs/sample/metatype.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,21 @@ typegates:
metagen:
targets:
main:
- generator: client_ts
path: ./ts/
typegraph_path: ../sample.ts
- generator: client_py
path: ./py/
typegraph_path: ../sample.ts
- generator: client_rs
path: ./rs/
typegraph_path: ../sample.ts
# skip_cargo_toml: true
skip_lib_rs: true
# - generator: client_ts
# path: ./ts/
# typegraph_path: ../sample.ts
# - generator: client_py
# path: ./py/
# typegraph_path: ../sample.ts
# - generator: client_rs
# path: ./rs/
# typegraph_path: ../sample.ts
# # skip_cargo_toml: true
# skip_lib_rs: true
- generator: client_ts
path: ./ts_upload/
typegraph_path: ../file_upload_sample.ts
- generator: client_rs
path: ./rs_upload/
typegraph_path: ../file_upload_sample.ts
skip_lib_rs: true
Loading

0 comments on commit 599586e

Please sign in to comment.