1use serde::{Deserialize, Serialize};
8use ulid::Ulid;
9
10use crate::UrlBuilder;
11pub use crate::traits::*;
12
13#[derive(Deserialize, Serialize, Clone, Debug)]
14#[serde(rename_all = "snake_case", tag = "kind")]
15pub enum PostAuthAction {
16 ContinueAuthorizationGrant {
17 id: Ulid,
18 },
19 ContinueDeviceCodeGrant {
20 id: Ulid,
21 },
22 ContinueCompatSsoLogin {
23 id: Ulid,
24 },
25 ChangePassword,
26 LinkUpstream {
27 id: Ulid,
28 },
29 ManageAccount {
30 #[serde(flatten)]
31 action: Option<AccountAction>,
32 },
33}
34
35impl PostAuthAction {
36 #[must_use]
37 pub const fn continue_grant(id: Ulid) -> Self {
38 PostAuthAction::ContinueAuthorizationGrant { id }
39 }
40
41 #[must_use]
42 pub const fn continue_device_code_grant(id: Ulid) -> Self {
43 PostAuthAction::ContinueDeviceCodeGrant { id }
44 }
45
46 #[must_use]
47 pub const fn continue_compat_sso_login(id: Ulid) -> Self {
48 PostAuthAction::ContinueCompatSsoLogin { id }
49 }
50
51 #[must_use]
52 pub const fn link_upstream(id: Ulid) -> Self {
53 PostAuthAction::LinkUpstream { id }
54 }
55
56 #[must_use]
57 pub const fn manage_account(action: Option<AccountAction>) -> Self {
58 PostAuthAction::ManageAccount { action }
59 }
60
61 pub fn go_next(&self, url_builder: &UrlBuilder) -> axum::response::Redirect {
62 match self {
63 Self::ContinueAuthorizationGrant { id } => url_builder.redirect(&Consent(*id)),
64 Self::ContinueDeviceCodeGrant { id } => {
65 url_builder.redirect(&DeviceCodeConsent::new(*id))
66 }
67 Self::ContinueCompatSsoLogin { id } => {
68 url_builder.redirect(&CompatLoginSsoComplete::new(*id, None))
69 }
70 Self::ChangePassword => url_builder.redirect(&AccountPasswordChange),
71 Self::LinkUpstream { id } => url_builder.redirect(&UpstreamOAuth2Link::new(*id)),
72 Self::ManageAccount { action } => url_builder.redirect(&Account {
73 action: action.clone(),
74 }),
75 }
76 }
77}
78
79#[derive(Default, Debug, Clone)]
81pub struct OidcConfiguration;
82
83impl SimpleRoute for OidcConfiguration {
84 const PATH: &'static str = "/.well-known/openid-configuration";
85}
86
87#[derive(Default, Debug, Clone)]
89pub struct Webfinger;
90
91impl SimpleRoute for Webfinger {
92 const PATH: &'static str = "/.well-known/webfinger";
93}
94
95pub struct ChangePasswordDiscovery;
97
98impl SimpleRoute for ChangePasswordDiscovery {
99 const PATH: &'static str = "/.well-known/change-password";
100}
101
102#[derive(Default, Debug, Clone)]
104pub struct OAuth2Keys;
105
106impl SimpleRoute for OAuth2Keys {
107 const PATH: &'static str = "/oauth2/keys.json";
108}
109
110#[derive(Default, Debug, Clone)]
112pub struct OidcUserinfo;
113
114impl SimpleRoute for OidcUserinfo {
115 const PATH: &'static str = "/oauth2/userinfo";
116}
117
118#[derive(Default, Debug, Clone)]
120pub struct OAuth2Introspection;
121
122impl SimpleRoute for OAuth2Introspection {
123 const PATH: &'static str = "/oauth2/introspect";
124}
125
126#[derive(Default, Debug, Clone)]
128pub struct OAuth2Revocation;
129
130impl SimpleRoute for OAuth2Revocation {
131 const PATH: &'static str = "/oauth2/revoke";
132}
133
134#[derive(Default, Debug, Clone)]
136pub struct OAuth2TokenEndpoint;
137
138impl SimpleRoute for OAuth2TokenEndpoint {
139 const PATH: &'static str = "/oauth2/token";
140}
141
142#[derive(Default, Debug, Clone)]
144pub struct OAuth2RegistrationEndpoint;
145
146impl SimpleRoute for OAuth2RegistrationEndpoint {
147 const PATH: &'static str = "/oauth2/registration";
148}
149
150#[derive(Default, Debug, Clone)]
152pub struct OAuth2AuthorizationEndpoint;
153
154impl SimpleRoute for OAuth2AuthorizationEndpoint {
155 const PATH: &'static str = "/authorize";
156}
157
158#[derive(Default, Debug, Clone)]
160pub struct Index;
161
162impl SimpleRoute for Index {
163 const PATH: &'static str = "/";
164}
165
166#[derive(Default, Debug, Clone)]
168pub struct Healthcheck;
169
170impl SimpleRoute for Healthcheck {
171 const PATH: &'static str = "/health";
172}
173
174#[derive(Default, Debug, Clone, Serialize, Deserialize)]
176pub struct Login {
177 #[serde(flatten)]
178 post_auth_action: Option<PostAuthAction>,
179
180 login_hint: Option<String>,
181}
182
183impl Route for Login {
184 type Query = Self;
185
186 fn route() -> &'static str {
187 "/login"
188 }
189
190 fn query(&self) -> Option<&Self::Query> {
191 Some(self)
192 }
193}
194
195impl Login {
196 #[must_use]
197 pub const fn and_then(action: PostAuthAction) -> Self {
198 Self {
199 post_auth_action: Some(action),
200 login_hint: None,
201 }
202 }
203
204 #[must_use]
205 pub const fn and_continue_grant(id: Ulid) -> Self {
206 Self {
207 post_auth_action: Some(PostAuthAction::continue_grant(id)),
208 login_hint: None,
209 }
210 }
211
212 #[must_use]
213 pub const fn and_continue_device_code_grant(id: Ulid) -> Self {
214 Self {
215 post_auth_action: Some(PostAuthAction::continue_device_code_grant(id)),
216 login_hint: None,
217 }
218 }
219
220 #[must_use]
221 pub const fn and_continue_compat_sso_login(id: Ulid) -> Self {
222 Self {
223 post_auth_action: Some(PostAuthAction::continue_compat_sso_login(id)),
224 login_hint: None,
225 }
226 }
227
228 #[must_use]
229 pub const fn and_link_upstream(id: Ulid) -> Self {
230 Self {
231 post_auth_action: Some(PostAuthAction::link_upstream(id)),
232 login_hint: None,
233 }
234 }
235
236 #[must_use]
237 pub fn with_login_hint(mut self, login_hint: String) -> Self {
238 self.login_hint = Some(login_hint);
239 self
240 }
241
242 #[must_use]
244 pub fn post_auth_action(&self) -> Option<&PostAuthAction> {
245 self.post_auth_action.as_ref()
246 }
247
248 pub fn go_next(&self, url_builder: &UrlBuilder) -> axum::response::Redirect {
249 match &self.post_auth_action {
250 Some(action) => action.go_next(url_builder),
251 None => url_builder.redirect(&Index),
252 }
253 }
254}
255
256impl From<Option<PostAuthAction>> for Login {
257 fn from(post_auth_action: Option<PostAuthAction>) -> Self {
258 Self {
259 post_auth_action,
260 login_hint: None,
261 }
262 }
263}
264
265#[derive(Default, Debug, Clone)]
267pub struct Logout;
268
269impl SimpleRoute for Logout {
270 const PATH: &'static str = "/logout";
271}
272
273#[derive(Default, Debug, Clone)]
275pub struct Register {
276 post_auth_action: Option<PostAuthAction>,
277}
278
279impl Register {
280 #[must_use]
281 pub fn and_then(action: PostAuthAction) -> Self {
282 Self {
283 post_auth_action: Some(action),
284 }
285 }
286
287 #[must_use]
288 pub fn and_continue_grant(data: Ulid) -> Self {
289 Self {
290 post_auth_action: Some(PostAuthAction::continue_grant(data)),
291 }
292 }
293
294 #[must_use]
295 pub fn and_continue_compat_sso_login(data: Ulid) -> Self {
296 Self {
297 post_auth_action: Some(PostAuthAction::continue_compat_sso_login(data)),
298 }
299 }
300
301 #[must_use]
303 pub fn post_auth_action(&self) -> Option<&PostAuthAction> {
304 self.post_auth_action.as_ref()
305 }
306
307 pub fn go_next(&self, url_builder: &UrlBuilder) -> axum::response::Redirect {
308 match &self.post_auth_action {
309 Some(action) => action.go_next(url_builder),
310 None => url_builder.redirect(&Index),
311 }
312 }
313}
314
315impl Route for Register {
316 type Query = PostAuthAction;
317
318 fn route() -> &'static str {
319 "/register"
320 }
321
322 fn query(&self) -> Option<&Self::Query> {
323 self.post_auth_action.as_ref()
324 }
325}
326
327impl From<Option<PostAuthAction>> for Register {
328 fn from(post_auth_action: Option<PostAuthAction>) -> Self {
329 Self { post_auth_action }
330 }
331}
332
333#[derive(Default, Debug, Clone, Serialize, Deserialize)]
335pub struct PasswordRegister {
336 username: Option<String>,
337
338 #[serde(flatten)]
339 post_auth_action: Option<PostAuthAction>,
340}
341
342impl PasswordRegister {
343 #[must_use]
344 pub fn and_then(mut self, action: PostAuthAction) -> Self {
345 self.post_auth_action = Some(action);
346 self
347 }
348
349 #[must_use]
350 pub fn and_continue_grant(mut self, data: Ulid) -> Self {
351 self.post_auth_action = Some(PostAuthAction::continue_grant(data));
352 self
353 }
354
355 #[must_use]
356 pub fn and_continue_compat_sso_login(mut self, data: Ulid) -> Self {
357 self.post_auth_action = Some(PostAuthAction::continue_compat_sso_login(data));
358 self
359 }
360
361 #[must_use]
363 pub fn post_auth_action(&self) -> Option<&PostAuthAction> {
364 self.post_auth_action.as_ref()
365 }
366
367 #[must_use]
369 pub fn username(&self) -> Option<&str> {
370 self.username.as_deref()
371 }
372
373 pub fn go_next(&self, url_builder: &UrlBuilder) -> axum::response::Redirect {
374 match &self.post_auth_action {
375 Some(action) => action.go_next(url_builder),
376 None => url_builder.redirect(&Index),
377 }
378 }
379}
380
381impl Route for PasswordRegister {
382 type Query = Self;
383
384 fn route() -> &'static str {
385 "/register/password"
386 }
387
388 fn query(&self) -> Option<&Self::Query> {
389 Some(self)
390 }
391}
392
393impl From<Option<PostAuthAction>> for PasswordRegister {
394 fn from(post_auth_action: Option<PostAuthAction>) -> Self {
395 Self {
396 username: None,
397 post_auth_action,
398 }
399 }
400}
401
402#[derive(Debug, Clone)]
404pub struct RegisterToken {
405 id: Ulid,
406}
407
408impl RegisterToken {
409 #[must_use]
410 pub fn new(id: Ulid) -> Self {
411 Self { id }
412 }
413}
414
415impl Route for RegisterToken {
416 type Query = ();
417 fn route() -> &'static str {
418 "/register/steps/{id}/token"
419 }
420
421 fn path(&self) -> std::borrow::Cow<'static, str> {
422 format!("/register/steps/{}/token", self.id).into()
423 }
424}
425
426#[derive(Debug, Clone)]
428pub struct RegisterDisplayName {
429 id: Ulid,
430}
431
432impl RegisterDisplayName {
433 #[must_use]
434 pub fn new(id: Ulid) -> Self {
435 Self { id }
436 }
437}
438
439impl Route for RegisterDisplayName {
440 type Query = ();
441 fn route() -> &'static str {
442 "/register/steps/{id}/display-name"
443 }
444
445 fn path(&self) -> std::borrow::Cow<'static, str> {
446 format!("/register/steps/{}/display-name", self.id).into()
447 }
448}
449
450#[derive(Debug, Clone)]
452pub struct RegisterVerifyEmail {
453 id: Ulid,
454}
455
456impl RegisterVerifyEmail {
457 #[must_use]
458 pub fn new(id: Ulid) -> Self {
459 Self { id }
460 }
461}
462
463impl Route for RegisterVerifyEmail {
464 type Query = ();
465 fn route() -> &'static str {
466 "/register/steps/{id}/verify-email"
467 }
468
469 fn path(&self) -> std::borrow::Cow<'static, str> {
470 format!("/register/steps/{}/verify-email", self.id).into()
471 }
472}
473
474#[derive(Debug, Clone)]
476pub struct RegisterFinish {
477 id: Ulid,
478}
479
480impl RegisterFinish {
481 #[must_use]
482 pub const fn new(id: Ulid) -> Self {
483 Self { id }
484 }
485}
486
487impl Route for RegisterFinish {
488 type Query = ();
489 fn route() -> &'static str {
490 "/register/steps/{id}/finish"
491 }
492
493 fn path(&self) -> std::borrow::Cow<'static, str> {
494 format!("/register/steps/{}/finish", self.id).into()
495 }
496}
497
498#[derive(Debug, Clone, Serialize, Deserialize)]
500#[serde(tag = "action")]
501pub enum AccountAction {
502 #[serde(rename = "org.matrix.profile")]
503 OrgMatrixProfile,
504 #[serde(rename = "profile")]
506 Profile,
507
508 #[serde(rename = "org.matrix.devices_list")]
509 OrgMatrixDevicesList,
510 #[serde(rename = "org.matrix.sessions_list")]
512 OrgMatrixSessionsList,
513 #[serde(rename = "sessions_list")]
515 SessionsList,
516
517 #[serde(rename = "org.matrix.device_view")]
518 OrgMatrixDeviceView { device_id: String },
519 #[serde(rename = "org.matrix.session_view")]
521 OrgMatrixSessionView { device_id: String },
522 #[serde(rename = "session_view")]
524 SessionView { device_id: String },
525
526 #[serde(rename = "org.matrix.device_delete")]
527 OrgMatrixDeviceDelete { device_id: String },
528 #[serde(rename = "org.matrix.session_end")]
530 OrgMatrixSessionEnd { device_id: String },
531 #[serde(rename = "session_end")]
533 SessionEnd { device_id: String },
534
535 #[serde(rename = "org.matrix.cross_signing_reset")]
536 OrgMatrixCrossSigningReset,
537}
538
539#[derive(Default, Debug, Clone)]
541pub struct Account {
542 action: Option<AccountAction>,
543}
544
545impl Route for Account {
546 type Query = AccountAction;
547
548 fn route() -> &'static str {
549 "/account/"
550 }
551
552 fn query(&self) -> Option<&Self::Query> {
553 self.action.as_ref()
554 }
555}
556
557#[derive(Default, Debug, Clone)]
559pub struct AccountWildcard;
560
561impl SimpleRoute for AccountWildcard {
562 const PATH: &'static str = "/account/{*rest}";
563}
564
565#[derive(Default, Debug, Clone)]
570pub struct AccountPasswordChange;
571
572impl SimpleRoute for AccountPasswordChange {
573 const PATH: &'static str = "/account/password/change";
574}
575
576#[derive(Debug, Clone)]
578pub struct Consent(pub Ulid);
579
580impl Route for Consent {
581 type Query = ();
582 fn route() -> &'static str {
583 "/consent/{grant_id}"
584 }
585
586 fn path(&self) -> std::borrow::Cow<'static, str> {
587 format!("/consent/{}", self.0).into()
588 }
589}
590
591pub struct CompatLogin;
593
594impl SimpleRoute for CompatLogin {
595 const PATH: &'static str = "/_matrix/client/{version}/login";
596}
597
598pub struct CompatLogout;
600
601impl SimpleRoute for CompatLogout {
602 const PATH: &'static str = "/_matrix/client/{version}/logout";
603}
604
605pub struct CompatLogoutAll;
607
608impl SimpleRoute for CompatLogoutAll {
609 const PATH: &'static str = "/_matrix/client/{version}/logout/all";
610}
611
612pub struct CompatRefresh;
614
615impl SimpleRoute for CompatRefresh {
616 const PATH: &'static str = "/_matrix/client/{version}/refresh";
617}
618
619pub struct CompatLoginSsoRedirect;
621
622impl SimpleRoute for CompatLoginSsoRedirect {
623 const PATH: &'static str = "/_matrix/client/{version}/login/sso/redirect";
624}
625
626pub struct CompatLoginSsoRedirectSlash;
631
632impl SimpleRoute for CompatLoginSsoRedirectSlash {
633 const PATH: &'static str = "/_matrix/client/{version}/login/sso/redirect/";
634}
635
636pub struct CompatLoginSsoRedirectIdp;
638
639impl SimpleRoute for CompatLoginSsoRedirectIdp {
640 const PATH: &'static str = "/_matrix/client/{version}/login/sso/redirect/{idp}";
641}
642
643#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
644#[serde(rename_all = "lowercase")]
645pub enum CompatLoginSsoAction {
646 Login,
647 Register,
648 #[serde(other)]
649 Unknown,
650}
651
652impl CompatLoginSsoAction {
653 #[must_use]
655 pub fn is_known(&self) -> bool {
656 !matches!(self, Self::Unknown)
657 }
658}
659
660#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
661pub struct CompatLoginSsoActionParams {
662 action: CompatLoginSsoAction,
663 #[serde(rename = "org.matrix.msc3824.action")]
666 unstable_action: CompatLoginSsoAction,
667}
668
669pub struct CompatLoginSsoComplete {
671 id: Ulid,
672 query: Option<CompatLoginSsoActionParams>,
673}
674
675impl CompatLoginSsoComplete {
676 #[must_use]
677 pub fn new(id: Ulid, action: Option<CompatLoginSsoAction>) -> Self {
678 Self {
679 id,
680 query: action.map(|action| CompatLoginSsoActionParams {
681 action,
682 unstable_action: action,
683 }),
684 }
685 }
686}
687
688impl Route for CompatLoginSsoComplete {
689 type Query = CompatLoginSsoActionParams;
690
691 fn query(&self) -> Option<&Self::Query> {
692 self.query.as_ref()
693 }
694
695 fn route() -> &'static str {
696 "/complete-compat-sso/{grant_id}"
697 }
698
699 fn path(&self) -> std::borrow::Cow<'static, str> {
700 format!("/complete-compat-sso/{}", self.id).into()
701 }
702}
703
704pub struct UpstreamOAuth2Authorize {
706 id: Ulid,
707 post_auth_action: Option<PostAuthAction>,
708}
709
710impl UpstreamOAuth2Authorize {
711 #[must_use]
712 pub const fn new(id: Ulid) -> Self {
713 Self {
714 id,
715 post_auth_action: None,
716 }
717 }
718
719 #[must_use]
720 pub fn and_then(mut self, action: PostAuthAction) -> Self {
721 self.post_auth_action = Some(action);
722 self
723 }
724}
725
726impl Route for UpstreamOAuth2Authorize {
727 type Query = PostAuthAction;
728 fn route() -> &'static str {
729 "/upstream/authorize/{provider_id}"
730 }
731
732 fn path(&self) -> std::borrow::Cow<'static, str> {
733 format!("/upstream/authorize/{}", self.id).into()
734 }
735
736 fn query(&self) -> Option<&Self::Query> {
737 self.post_auth_action.as_ref()
738 }
739}
740
741pub struct UpstreamOAuth2Callback {
743 id: Ulid,
744}
745
746impl UpstreamOAuth2Callback {
747 #[must_use]
748 pub const fn new(id: Ulid) -> Self {
749 Self { id }
750 }
751}
752
753impl Route for UpstreamOAuth2Callback {
754 type Query = ();
755 fn route() -> &'static str {
756 "/upstream/callback/{provider_id}"
757 }
758
759 fn path(&self) -> std::borrow::Cow<'static, str> {
760 format!("/upstream/callback/{}", self.id).into()
761 }
762}
763
764pub struct UpstreamOAuth2Link {
766 id: Ulid,
767}
768
769impl UpstreamOAuth2Link {
770 #[must_use]
771 pub const fn new(id: Ulid) -> Self {
772 Self { id }
773 }
774}
775
776impl Route for UpstreamOAuth2Link {
777 type Query = ();
778 fn route() -> &'static str {
779 "/upstream/link/{link_id}"
780 }
781
782 fn path(&self) -> std::borrow::Cow<'static, str> {
783 format!("/upstream/link/{}", self.id).into()
784 }
785}
786
787pub struct UpstreamOAuth2BackchannelLogout {
789 id: Ulid,
790}
791
792impl UpstreamOAuth2BackchannelLogout {
793 #[must_use]
794 pub const fn new(id: Ulid) -> Self {
795 Self { id }
796 }
797}
798
799impl Route for UpstreamOAuth2BackchannelLogout {
800 type Query = ();
801 fn route() -> &'static str {
802 "/upstream/backchannel-logout/{provider_id}"
803 }
804
805 fn path(&self) -> std::borrow::Cow<'static, str> {
806 format!("/upstream/backchannel-logout/{}", self.id).into()
807 }
808}
809
810#[derive(Default, Serialize, Deserialize, Debug, Clone)]
812pub struct DeviceCodeLink {
813 code: Option<String>,
814}
815
816impl DeviceCodeLink {
817 #[must_use]
818 pub fn with_code(code: String) -> Self {
819 Self { code: Some(code) }
820 }
821}
822
823impl Route for DeviceCodeLink {
824 type Query = DeviceCodeLink;
825 fn route() -> &'static str {
826 "/link"
827 }
828
829 fn query(&self) -> Option<&Self::Query> {
830 Some(self)
831 }
832}
833
834#[derive(Default, Serialize, Deserialize, Debug, Clone)]
836pub struct DeviceCodeConsent {
837 id: Ulid,
838}
839
840impl Route for DeviceCodeConsent {
841 type Query = ();
842 fn route() -> &'static str {
843 "/device/{device_code_id}"
844 }
845
846 fn path(&self) -> std::borrow::Cow<'static, str> {
847 format!("/device/{}", self.id).into()
848 }
849}
850
851impl DeviceCodeConsent {
852 #[must_use]
853 pub fn new(id: Ulid) -> Self {
854 Self { id }
855 }
856}
857
858#[derive(Default, Serialize, Deserialize, Debug, Clone)]
860pub struct OAuth2DeviceAuthorizationEndpoint;
861
862impl SimpleRoute for OAuth2DeviceAuthorizationEndpoint {
863 const PATH: &'static str = "/oauth2/device";
864}
865
866#[derive(Default, Serialize, Deserialize, Debug, Clone)]
868pub struct AccountRecoveryStart;
869
870impl SimpleRoute for AccountRecoveryStart {
871 const PATH: &'static str = "/recover";
872}
873
874#[derive(Default, Serialize, Deserialize, Debug, Clone)]
876pub struct AccountRecoveryProgress {
877 session_id: Ulid,
878}
879
880impl AccountRecoveryProgress {
881 #[must_use]
882 pub fn new(session_id: Ulid) -> Self {
883 Self { session_id }
884 }
885}
886
887impl Route for AccountRecoveryProgress {
888 type Query = ();
889 fn route() -> &'static str {
890 "/recover/progress/{session_id}"
891 }
892
893 fn path(&self) -> std::borrow::Cow<'static, str> {
894 format!("/recover/progress/{}", self.session_id).into()
895 }
896}
897
898#[derive(Default, Serialize, Deserialize, Debug, Clone)]
901pub struct AccountRecoveryFinish {
902 ticket: String,
903}
904
905impl AccountRecoveryFinish {
906 #[must_use]
907 pub fn new(ticket: String) -> Self {
908 Self { ticket }
909 }
910}
911
912impl Route for AccountRecoveryFinish {
913 type Query = AccountRecoveryFinish;
914
915 fn route() -> &'static str {
916 "/account/password/recovery"
917 }
918
919 fn query(&self) -> Option<&Self::Query> {
920 Some(self)
921 }
922}
923
924pub struct StaticAsset {
926 path: String,
927}
928
929impl StaticAsset {
930 #[must_use]
931 pub fn new(path: String) -> Self {
932 Self { path }
933 }
934}
935
936impl Route for StaticAsset {
937 type Query = ();
938 fn route() -> &'static str {
939 "/assets/"
940 }
941
942 fn path(&self) -> std::borrow::Cow<'static, str> {
943 format!("/assets/{}", self.path).into()
944 }
945}
946
947pub struct GraphQL;
949
950impl SimpleRoute for GraphQL {
951 const PATH: &'static str = "/graphql";
952}
953
954pub struct GraphQLPlayground;
956
957impl SimpleRoute for GraphQLPlayground {
958 const PATH: &'static str = "/graphql/playground";
959}
960
961pub struct ApiSpec;
963
964impl SimpleRoute for ApiSpec {
965 const PATH: &'static str = "/api/spec.json";
966}
967
968pub struct ApiDoc;
970
971impl SimpleRoute for ApiDoc {
972 const PATH: &'static str = "/api/doc/";
973}
974
975pub struct ApiDocCallback;
977
978impl SimpleRoute for ApiDocCallback {
979 const PATH: &'static str = "/api/doc/oauth2-callback";
980}