async_graphql/types/
upload.rs1use std::{borrow::Cow, ops::Deref, sync::Arc};
2
3use futures_util::AsyncRead;
4
5use crate::{
6 Context, InputType, InputValueError, InputValueResult, Value, registry, registry::MetaTypeId,
7};
8
9pub struct UploadValue {
11 pub filename: String,
13 pub content_type: Option<String>,
15 #[cfg(feature = "tempfile")]
17 pub content: std::fs::File,
18 #[cfg(not(feature = "tempfile"))]
20 pub content: bytes::Bytes,
21}
22
23impl UploadValue {
24 pub fn try_clone(&self) -> std::io::Result<Self> {
31 #[cfg(feature = "tempfile")]
32 {
33 Ok(Self {
34 filename: self.filename.clone(),
35 content_type: self.content_type.clone(),
36 content: self.content.try_clone()?,
37 })
38 }
39
40 #[cfg(not(feature = "tempfile"))]
41 {
42 Ok(Self {
43 filename: self.filename.clone(),
44 content_type: self.content_type.clone(),
45 content: self.content.clone(),
46 })
47 }
48 }
49
50 pub fn into_async_read(self) -> impl AsyncRead + Sync + Send + 'static {
52 #[cfg(feature = "tempfile")]
53 {
54 blocking::Unblock::new(self.content)
55 }
56
57 #[cfg(not(feature = "tempfile"))]
58 {
59 futures_util::io::Cursor::new(self.content)
60 }
61 }
62
63 pub fn size(&self) -> std::io::Result<u64> {
65 #[cfg(feature = "tempfile")]
66 {
67 self.content.metadata().map(|meta| meta.len())
68 }
69
70 #[cfg(not(feature = "tempfile"))]
71 {
72 Ok(self.content.len() as u64)
73 }
74 }
75}
76
77#[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Hash)]
117pub struct Upload(pub usize);
118
119impl Upload {
120 pub fn value(&self, ctx: &Context<'_>) -> std::io::Result<UploadValue> {
122 ctx.query_env.uploads[self.0].try_clone()
123 }
124}
125
126impl Deref for Upload {
127 type Target = usize;
128
129 fn deref(&self) -> &Self::Target {
130 &self.0
131 }
132}
133
134impl InputType for Upload {
135 type RawValueType = Self;
136
137 fn type_name() -> Cow<'static, str> {
138 Cow::Borrowed("Upload")
139 }
140
141 fn create_type_info(registry: &mut registry::Registry) -> String {
142 registry.create_input_type::<Self, _>(MetaTypeId::Scalar, |_| registry::MetaType::Scalar {
143 name: Self::type_name().to_string(),
144 description: Some("A multipart file upload".to_string()),
145 is_valid: Some(Arc::new(|value| matches!(value, Value::String(_)))),
146 visible: None,
147 inaccessible: false,
148 tags: Default::default(),
149 specified_by_url: Some(
150 "https://github.com/jaydenseric/graphql-multipart-request-spec".to_string(),
151 ),
152 directive_invocations: Default::default(),
153 requires_scopes: Default::default(),
154 })
155 }
156
157 fn parse(value: Option<Value>) -> InputValueResult<Self> {
158 const PREFIX: &str = "#__graphql_file__:";
159 let value = value.unwrap_or_default();
160 if let Value::String(s) = &value
161 && let Some(filename) = s.strip_prefix(PREFIX)
162 {
163 return Ok(Upload(filename.parse::<usize>().unwrap()));
164 }
165 Err(InputValueError::expected_type(value))
166 }
167
168 fn to_value(&self) -> Value {
169 Value::Null
170 }
171
172 fn as_raw_value(&self) -> Option<&Self::RawValueType> {
173 Some(self)
174 }
175}