1use std::{borrow::Cow, collections::BTreeMap, panic::Location};
4
5use futures_util::{stream, FutureExt, StreamExt, TryStreamExt};
6use rspc_legacy::internal::{Layer, RequestContext, ValueOrStream};
7use rspc_procedure::{ProcedureStream, ResolverError};
8use serde_json::Value;
9use specta::{
10 datatype::{DataType, EnumRepr, EnumVariant, LiteralType},
11 NamedType, Type,
12};
13
14use crate::{
15 procedure::{ErasedProcedure, ProcedureType},
16 types::TypesOrType,
17 util::literal_object,
18 ProcedureKind,
19};
20
21impl<TCtx> From<rspc_legacy::Router<TCtx>> for crate::Router<TCtx> {
22 fn from(router: rspc_legacy::Router<TCtx>) -> Self {
23 let mut r = crate::Router::new();
24
25 let (queries, mutations, subscriptions, mut type_map) = router.into_parts();
26
27 let bridged_procedures = queries
28 .into_iter()
29 .map(|v| (ProcedureKind::Query, v))
30 .chain(mutations.into_iter().map(|v| (ProcedureKind::Mutation, v)))
31 .chain(
32 subscriptions
33 .into_iter()
34 .map(|v| (ProcedureKind::Subscription, v)),
35 )
36 .map(|(kind, (key, p))| {
37 (
38 key.split(".")
39 .map(|s| s.to_string().into())
40 .collect::<Vec<Cow<'static, str>>>(),
41 ErasedProcedure {
42 kind,
43 location: Location::caller().clone(), setup: Default::default(),
45 inner: Box::new(move |_, types| {
46 (
47 layer_to_procedure(key.to_string(), kind, p.exec),
48 ProcedureType {
49 kind,
50 input: p.ty.arg_ty.clone(),
51 output: p.ty.result_ty.clone(),
52 error: specta::datatype::DataType::Unknown,
53 location: Location::caller().clone(), },
57 )
58 }),
59 },
60 )
61 });
62
63 for (key, procedure) in bridged_procedures {
64 if r.procedures.insert(key.clone(), procedure).is_some() {
65 panic!("Attempted to mount '{key:?}' multiple times.\nrspc no longer supports different operations (query/mutation/subscription) with overlapping names.")
66 }
67 }
68
69 r.types.extend(&mut type_map);
70 r
71 }
72}
73
74pub(crate) fn layer_to_procedure<TCtx: 'static>(
75 path: String,
76 kind: ProcedureKind,
77 value: Box<dyn Layer<TCtx>>,
78) -> rspc_procedure::Procedure<TCtx> {
79 rspc_procedure::Procedure::new(move |ctx, input| {
80 let result = input.deserialize::<Value>().and_then(|input| {
81 value
82 .call(
83 ctx,
84 input,
85 RequestContext {
86 kind: match kind {
87 ProcedureKind::Query => rspc_legacy::internal::ProcedureKind::Query,
88 ProcedureKind::Mutation => {
89 rspc_legacy::internal::ProcedureKind::Mutation
90 }
91 ProcedureKind::Subscription => {
92 rspc_legacy::internal::ProcedureKind::Subscription
93 }
94 },
95 path: path.clone(),
96 },
97 )
98 .map_err(|err| {
99 let err: rspc_legacy::Error = err.into();
100 ResolverError::new(
101 (), Some(rspc_procedure::LegacyErrorInterop(err.message().into())),
103 )
104 .into()
105 })
106 });
107
108 match result {
109 Ok(result) => ProcedureStream::from_stream(
110 async move {
111 match result.into_value_or_stream().await {
112 Ok(ValueOrStream::Value(value)) => {
113 stream::once(async { Ok(value) }).boxed()
114 }
115 Ok(ValueOrStream::Stream(s)) => s
116 .map_err(|err| {
117 let err = rspc_legacy::Error::from(err);
118 ResolverError::new(
119 (), Some(rspc_procedure::LegacyErrorInterop(err.message().into())),
121 )
122 .into()
123 })
124 .boxed(),
125 Err(err) => {
126 let err: rspc_legacy::Error = err.into();
127 let err = ResolverError::new(err.message().to_string(), err.cause());
128 stream::once(async { Err(err.into()) }).boxed()
129 }
130 }
131 }
132 .into_stream()
133 .flatten(),
134 ),
135 Err(err) => {
136 ProcedureStream::from_stream(stream::once(async move { Err::<(), _>(err) }))
137 }
138 }
139 })
140}
141
142fn map_method(
143 kind: ProcedureKind,
144 p: &BTreeMap<Vec<Cow<'static, str>>, ProcedureType>,
145) -> Vec<(Cow<'static, str>, EnumVariant)> {
146 p.iter()
147 .filter(|(_, p)| p.kind == kind)
148 .map(|(key, p)| {
149 let key = key.join(".").to_string();
150 (
151 key.clone().into(),
152 specta::internal::construct::enum_variant(
153 false,
154 None,
155 "".into(),
156 specta::internal::construct::enum_variant_unnamed(vec![
157 specta::internal::construct::field(
158 false,
159 false,
160 None,
161 "".into(),
162 Some(literal_object(
163 "".into(),
164 None,
165 vec![
166 ("key".into(), LiteralType::String(key.clone()).into()),
167 ("input".into(), p.input.clone()),
168 ("result".into(), p.output.clone()),
169 ]
170 .into_iter(),
171 )),
172 ),
173 ]),
174 ),
175 )
176 })
177 .collect::<Vec<_>>()
178}
179
180pub(crate) fn construct_legacy_bindings_type(
182 map: &BTreeMap<Cow<'static, str>, TypesOrType>,
183) -> Vec<(Cow<'static, str>, DataType)> {
184 #[derive(Type)]
185 struct Queries;
186 #[derive(Type)]
187 struct Mutations;
188 #[derive(Type)]
189 struct Subscriptions;
190
191 let mut p = BTreeMap::new();
192 for (k, v) in map {
193 flatten_procedures_for_legacy(&mut p, vec![k.clone()], v.clone());
194 }
195
196 vec![
197 (
198 "queries".into(),
199 specta::internal::construct::r#enum(
200 "Queries".into(),
201 Queries::sid(),
202 EnumRepr::Untagged,
203 false,
204 Default::default(),
205 map_method(ProcedureKind::Query, &p),
206 )
207 .into(),
208 ),
209 (
210 "mutations".into(),
211 specta::internal::construct::r#enum(
212 "Mutations".into(),
213 Mutations::sid(),
214 EnumRepr::Untagged,
215 false,
216 Default::default(),
217 map_method(ProcedureKind::Mutation, &p),
218 )
219 .into(),
220 ),
221 (
222 "subscriptions".into(),
223 specta::internal::construct::r#enum(
224 "Subscriptions".into(),
225 Subscriptions::sid(),
226 EnumRepr::Untagged,
227 false,
228 Default::default(),
229 map_method(ProcedureKind::Subscription, &p),
230 )
231 .into(),
232 ),
233 ]
234}
235
236fn flatten_procedures_for_legacy(
237 p: &mut BTreeMap<Vec<Cow<'static, str>>, ProcedureType>,
238 key: Vec<Cow<'static, str>>,
239 item: TypesOrType,
240) {
241 match item {
242 TypesOrType::Type(ty) => {
243 p.insert(key, ty);
244 }
245 TypesOrType::Types(types) => {
246 for (k, v) in types {
247 let mut key = key.clone();
248 key.push(k.clone());
249 flatten_procedures_for_legacy(p, key, v);
250 }
251 }
252 }
253}