SQL_CREATE_USER = "insert into users(email,name) values(lower($1),$2) returning id,email,name"
Name = Annotated[str, StringConstraints(strip_whitespace=True, min_length=1, max_length=120)]
class UserIn(BaseModel):
email: EmailStr
name: Name
class UserOut(UserIn):
id: int
@router.post("/users", response_model=UserOut, status_code=201)
async def create_user(u: UserIn) -> UserOut:
try:
row = await pool.fetchrow(SQL_CREATE_USER, str(u.email), u.name)
except UniqueViolationError as exc:
raise HTTPException(status_code=409, detail="email already exists") from exc
if row is None:
raise HTTPException(status_code=500, detail="insert failed")
return UserOut.model_validate(dict(row))
#[derive(Deserialize, Validate)]
#[serde(deny_unknown_fields)]
pub struct UserIn {
#[validate(email)]
pub email: String,
#[validate(custom(function = "valid_name"))]
pub name: String,
}
pub async fn create_user(
State(pool): State<PgPool>,
ValidatedJson(u): ValidatedJson<UserIn>,
) -> Result<(StatusCode, Json<UserOut>), ApiError> {
let user = sqlx::query_as!(UserOut,
"insert into users(email,name) values(lower($1),$2) returning id,email,name",
u.email.as_str(),
u.name.trim(),
)
.fetch_one(&pool)
.await
.map_err(ApiError::from_db)?;
Ok((StatusCode::CREATED, Json(user)))
}