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