async_graphql/types/
upload.rs1use std::{borrow::Cow, io::Read, ops::Deref, sync::Arc};
2
3#[cfg(feature = "unblock")]
4use futures_util::io::AsyncRead;
5
6use crate::{
7 registry, registry::MetaTypeId, Context, InputType, InputValueError, InputValueResult, Value,
8};
9
10pub struct UploadValue {
12 pub filename: String,
14 pub content_type: Option<String>,
16 #[cfg(feature = "tempfile")]
18 pub content: std::fs::File,
19 #[cfg(not(feature = "tempfile"))]
21 pub content: bytes::Bytes,
22}
23
24impl UploadValue {
25 pub fn try_clone(&self) -> std::io::Result<Self> {
32 #[cfg(feature = "tempfile")]
33 {
34 Ok(Self {
35 filename: self.filename.clone(),
36 content_type: self.content_type.clone(),
37 content: self.content.try_clone()?,
38 })
39 }
40
41 #[cfg(not(feature = "tempfile"))]
42 {
43 Ok(Self {
44 filename: self.filename.clone(),
45 content_type: self.content_type.clone(),
46 content: self.content.clone(),
47 })
48 }
49 }
50
51 pub fn into_read(self) -> impl Read + Sync + Send + 'static {
55 #[cfg(feature = "tempfile")]
56 {
57 self.content
58 }
59
60 #[cfg(not(feature = "tempfile"))]
61 {
62 std::io::Cursor::new(self.content)
63 }
64 }
65
66 #[cfg(feature = "unblock")]
68 #[cfg_attr(docsrs, doc(cfg(feature = "unblock")))]
69 pub fn into_async_read(self) -> impl AsyncRead + Sync + Send + 'static {
70 #[cfg(feature = "tempfile")]
71 {
72 blocking::Unblock::new(self.content)
73 }
74
75 #[cfg(not(feature = "tempfile"))]
76 {
77 std::io::Cursor::new(self.content)
78 }
79 }
80
81 pub fn size(&self) -> std::io::Result<u64> {
83 #[cfg(feature = "tempfile")]
84 {
85 self.content.metadata().map(|meta| meta.len())
86 }
87
88 #[cfg(not(feature = "tempfile"))]
89 {
90 Ok(self.content.len() as u64)
91 }
92 }
93}
94
95#[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Hash)]
135pub struct Upload(pub usize);
136
137impl Upload {
138 pub fn value(&self, ctx: &Context<'_>) -> std::io::Result<UploadValue> {
140 ctx.query_env.uploads[self.0].try_clone()
141 }
142}
143
144impl Deref for Upload {
145 type Target = usize;
146
147 fn deref(&self) -> &Self::Target {
148 &self.0
149 }
150}
151
152impl InputType for Upload {
153 type RawValueType = Self;
154
155 fn type_name() -> Cow<'static, str> {
156 Cow::Borrowed("Upload")
157 }
158
159 fn create_type_info(registry: &mut registry::Registry) -> String {
160 registry.create_input_type::<Self, _>(MetaTypeId::Scalar, |_| registry::MetaType::Scalar {
161 name: Self::type_name().to_string(),
162 description: None,
163 is_valid: Some(Arc::new(|value| matches!(value, Value::String(_)))),
164 visible: None,
165 inaccessible: false,
166 tags: Default::default(),
167 specified_by_url: Some(
168 "https://github.com/jaydenseric/graphql-multipart-request-spec".to_string(),
169 ),
170 directive_invocations: Default::default(),
171 })
172 }
173
174 fn parse(value: Option<Value>) -> InputValueResult<Self> {
175 const PREFIX: &str = "#__graphql_file__:";
176 let value = value.unwrap_or_default();
177 if let Value::String(s) = &value {
178 if let Some(filename) = s.strip_prefix(PREFIX) {
179 return Ok(Upload(filename.parse::<usize>().unwrap()));
180 }
181 }
182 Err(InputValueError::expected_type(value))
183 }
184
185 fn to_value(&self) -> Value {
186 Value::Null
187 }
188
189 fn as_raw_value(&self) -> Option<&Self::RawValueType> {
190 Some(self)
191 }
192}