better_auth_api/plugins/device_authorization/
request_fields.rs1use better_auth_core::{AuthError, AuthResult};
2use indexmap::IndexMap;
3use serde::{Deserialize, Serialize};
4use serde_json::{Map, Value, json};
5use std::{future::Future, pin::Pin, sync::Arc};
6
7#[derive(Clone, Debug, Serialize, Deserialize)]
9pub struct DeviceRequestIssue {
10 pub message: String,
12 #[serde(default)]
14 pub path: Vec<Value>,
15 #[serde(default, flatten)]
17 pub details: Map<String, Value>,
18}
19
20impl DeviceRequestIssue {
21 pub(super) fn invalid_type(field: Option<&str>, expected: &str, value: Option<&Value>) -> Self {
22 Self {
23 message: format!(
24 "Invalid input: expected {expected}, received {}",
25 crate::plugins::json_body::type_name(value)
26 ),
27 path: field.into_iter().map(|field| json!(field)).collect(),
28 details: Map::from_iter([
29 ("code".into(), json!("invalid_type")),
30 ("expected".into(), json!(expected)),
31 ]),
32 }
33 }
34
35 pub(super) fn message(&self) -> String {
36 let mut path = String::from("body");
37 for segment in &self.path {
38 let key = segment.get("key").unwrap_or(segment);
39 path.push('.');
40 match key {
41 Value::String(key) => path.push_str(key),
42 key => path.push_str(&key.to_string()),
43 }
44 }
45 format!("[{path}] {}", self.message)
46 }
47}
48
49pub enum DeviceFieldValidation {
51 Value(Option<Value>),
53 Issues(Vec<DeviceRequestIssue>),
55}
56
57type FieldResult = AuthResult<DeviceFieldValidation>;
58type FieldFuture = Pin<Box<dyn Future<Output = FieldResult> + Send>>;
59
60#[derive(Clone)]
62pub struct DeviceRequestField {
63 callback: Callback,
64 openapi: Option<RequestFieldSchema>,
65}
66
67#[derive(Clone)]
68struct RequestFieldSchema {
69 schema: Value,
70 required: bool,
71}
72
73#[derive(Clone)]
74enum Callback {
75 Sync(Arc<dyn Fn(Option<Value>) -> FieldResult + Send + Sync>),
76 Async(Arc<dyn Fn(Option<Value>) -> FieldFuture + Send + Sync>),
77}
78
79impl DeviceRequestField {
80 pub fn new(callback: impl Fn(Option<Value>) -> FieldResult + Send + Sync + 'static) -> Self {
82 Self {
83 callback: Callback::Sync(Arc::new(callback)),
84 openapi: None,
85 }
86 }
87
88 pub fn new_async<F, Fut>(callback: F) -> Self
90 where
91 F: Fn(Option<Value>) -> Fut + Send + Sync + 'static,
92 Fut: Future<Output = FieldResult> + Send + 'static,
93 {
94 Self {
95 callback: Callback::Async(Arc::new(move |value| Box::pin(callback(value)))),
96 openapi: None,
97 }
98 }
99
100 pub fn openapi_schema(mut self, schema: Value, required: bool) -> Self {
105 self.openapi = Some(RequestFieldSchema { schema, required });
106 self
107 }
108
109 pub(super) async fn validate(&self, value: Option<Value>) -> FieldResult {
110 match &self.callback {
111 Callback::Sync(callback) => callback(value),
112 Callback::Async(callback) => callback(value).await,
113 }
114 }
115}
116
117type ValidationErrorCallback = dyn Fn(&[DeviceRequestIssue]) -> AuthResult<()> + Send + Sync;
118
119#[derive(Clone, Default)]
121pub struct DeviceRequestFields {
122 pub(super) fields: IndexMap<String, DeviceRequestField>,
123 on_validation_error: Option<Arc<ValidationErrorCallback>>,
124}
125
126impl DeviceRequestFields {
127 pub fn new() -> Self {
129 Self::default()
130 }
131
132 pub fn field(mut self, name: impl Into<String>, field: DeviceRequestField) -> AuthResult<Self> {
134 let name = name.into();
135 if matches!(name.as_str(), "client_id" | "user_id" | "scope") {
136 return Err(AuthError::config(format!(
137 "Device authorization grant request fields must be additional and cannot redefine request fields: {name}",
138 )));
139 }
140 let _ = self.fields.insert(name, field);
141 Ok(self)
142 }
143
144 pub fn on_validation_error(
146 mut self,
147 callback: impl Fn(&[DeviceRequestIssue]) -> AuthResult<()> + Send + Sync + 'static,
148 ) -> Self {
149 self.on_validation_error = Some(Arc::new(callback));
150 self
151 }
152
153 pub(super) fn report(&self, issues: &[DeviceRequestIssue]) -> AuthResult<()> {
154 if let Some(callback) = &self.on_validation_error {
155 callback(issues)?;
156 }
157 Ok(())
158 }
159
160 pub(super) fn openapi(&self) -> (Map<String, Value>, Vec<String>) {
161 let mut properties = Map::new();
162 let mut required = Vec::new();
163 for (name, field) in &self.fields {
164 if let Some(metadata) = &field.openapi {
165 let _ = properties.insert(name.clone(), metadata.schema.clone());
166 if metadata.required {
167 required.push(name.clone());
168 }
169 }
170 }
171 (properties, required)
172 }
173}