33from dataclasses import dataclass
44from typing import Any
55
6- from sanic import HTTPResponse , Request , empty , json
6+ from sanic import HTTPResponse , Request , empty
77from sanic .response import JSONResponse
88from sanic_ext import validate
99from ulid import ULID
1313from renku_data_services .base_api .auth import authenticate
1414from renku_data_services .base_api .blueprint import BlueprintFactoryResponse , CustomBlueprint
1515from renku_data_services .base_api .misc import validate_query
16+ from renku_data_services .base_models .validation import validated_json
1617from renku_data_services .storage import apispec , models
1718from renku_data_services .storage .db import StorageRepository , StorageV2Repository
1819from renku_data_services .storage .rclone import RCloneValidator
@@ -49,7 +50,9 @@ async def _get(
4950 storage : list [models .CloudStorage ]
5051 storage = await self .storage_repo .get_storage (user = user , project_id = query .project_id )
5152
52- return json ([dump_storage_with_sensitive_fields (s , validator ) for s in storage ])
53+ return validated_json (
54+ apispec .StorageGetResponse , [dump_storage_with_sensitive_fields (s , validator ) for s in storage ]
55+ )
5356
5457 return "/storage" , ["GET" ], _get
5558
@@ -65,7 +68,7 @@ async def _get_one(
6568 ) -> JSONResponse :
6669 storage = await self .storage_repo .get_storage_by_id (storage_id , user = user )
6770
68- return json ( dump_storage_with_sensitive_fields (storage , validator ))
71+ return validated_json ( apispec . CloudStorageGet , dump_storage_with_sensitive_fields (storage , validator ))
6972
7073 return "/storage/<storage_id:ulid>" , ["GET" ], _get_one
7174
@@ -96,7 +99,7 @@ async def _post(request: Request, user: base_models.APIUser, validator: RCloneVa
9699 validator .validate (storage .configuration .model_dump ())
97100
98101 res = await self .storage_repo .insert_storage (storage = storage , user = user )
99- return json ( dump_storage_with_sensitive_fields (res , validator ), 201 )
102+ return validated_json ( apispec . CloudStorageGet , dump_storage_with_sensitive_fields (res , validator ), 201 )
100103
101104 return "/storage" , ["POST" ], _post
102105
@@ -131,7 +134,7 @@ async def _put(
131134 body_dict = new_storage .model_dump ()
132135 del body_dict ["storage_id" ]
133136 res = await self .storage_repo .update_storage (storage_id = storage_id , user = user , ** body_dict )
134- return json ( dump_storage_with_sensitive_fields (res , validator ))
137+ return validated_json ( apispec . CloudStorageGet , dump_storage_with_sensitive_fields (res , validator ))
135138
136139 return "/storage/<storage_id:ulid>" , ["PUT" ], _put
137140
@@ -161,7 +164,7 @@ async def _patch(
161164 body_dict = body .model_dump (exclude_none = True )
162165
163166 res = await self .storage_repo .update_storage (storage_id = storage_id , user = user , ** body_dict )
164- return json ( dump_storage_with_sensitive_fields (res , validator ))
167+ return validated_json ( apispec . CloudStorageGet , dump_storage_with_sensitive_fields (res , validator ))
165168
166169 return "/storage/<storage_id:ulid>" , ["PATCH" ], _patch
167170
@@ -195,9 +198,11 @@ async def _get(
195198 query : apispec .StorageV2Params ,
196199 ) -> JSONResponse :
197200 storage : list [models .CloudStorage ]
198- storage = await self .storage_v2_repo .get_storage (user = user , project_id = query .project_id )
201+ storage = await self .storage_v2_repo .get_storage (user = user , project_id = ULID . from_str ( query .project_id ) )
199202
200- return json ([dump_storage_with_sensitive_fields (s , validator ) for s in storage ])
203+ return validated_json (
204+ apispec .StoragesV2GetResponse , [dump_storage_with_sensitive_fields (s , validator ) for s in storage ]
205+ )
201206
202207 return "/storages_v2" , ["GET" ], _get
203208
@@ -213,7 +218,7 @@ async def _get_one(
213218 ) -> JSONResponse :
214219 storage = await self .storage_v2_repo .get_storage_by_id (storage_id , user = user )
215220
216- return json ( dump_storage_with_sensitive_fields (storage , validator ))
221+ return validated_json ( apispec . CloudStorageGet , dump_storage_with_sensitive_fields (storage , validator ))
217222
218223 return "/storages_v2/<storage_id:ulid>" , ["GET" ], _get_one
219224
@@ -244,7 +249,7 @@ async def _post(request: Request, user: base_models.APIUser, validator: RCloneVa
244249 validator .validate (storage .configuration .model_dump ())
245250
246251 res = await self .storage_v2_repo .insert_storage (storage = storage , user = user )
247- return json ( dump_storage_with_sensitive_fields (res , validator ), 201 )
252+ return validated_json ( apispec . CloudStorageGet , dump_storage_with_sensitive_fields (res , validator ), 201 )
248253
249254 return "/storages_v2" , ["POST" ], _post
250255
@@ -274,7 +279,7 @@ async def _patch(
274279 body_dict = body .model_dump (exclude_none = True )
275280
276281 res = await self .storage_v2_repo .update_storage (storage_id = storage_id , user = user , ** body_dict )
277- return json ( dump_storage_with_sensitive_fields (res , validator ))
282+ return validated_json ( apispec . CloudStorageGet , dump_storage_with_sensitive_fields (res , validator ))
278283
279284 return "/storages_v2/<storage_id:ulid>" , ["PATCH" ], _patch
280285
@@ -297,29 +302,19 @@ def get(self) -> BlueprintFactoryResponse:
297302 """Get cloud storage for a repository."""
298303
299304 async def _get (_ : Request , validator : RCloneValidator ) -> JSONResponse :
300- return json ( validator .asdict ())
305+ return validated_json ( apispec . RCloneSchema , validator .asdict ())
301306
302307 return "/storage_schema" , ["GET" ], _get
303308
304309 def test_connection (self ) -> BlueprintFactoryResponse :
305310 """Validate an RClone config."""
306311
307- async def _test_connection (request : Request , validator : RCloneValidator ) -> HTTPResponse :
308- if not request .json :
309- raise errors .ValidationError (message = "The request body is empty. Please provide a valid JSON object." )
310- if not isinstance (request .json , dict ):
311- raise errors .ValidationError (message = "The request body is not a valid JSON object." )
312- if not request .json .get ("configuration" ):
313- raise errors .ValidationError (message = "No 'configuration' sent." )
314- if not isinstance (request .json .get ("configuration" ), dict ):
315- config_type = type (request .json .get ("configuration" ))
316- raise errors .ValidationError (
317- message = f"The R clone configuration should be a dictionary, not { config_type .__name__ } "
318- )
319- if not request .json .get ("source_path" ):
320- raise errors .ValidationError (message = "'source_path' is required to test the connection." )
321- validator .validate (request .json ["configuration" ], keep_sensitive = True )
322- result = await validator .test_connection (request .json ["configuration" ], request .json ["source_path" ])
312+ @validate (json = apispec .StorageSchemaTestConnectionPostRequest )
313+ async def _test_connection (
314+ request : Request , validator : RCloneValidator , body : apispec .StorageSchemaTestConnectionPostRequest
315+ ) -> HTTPResponse :
316+ validator .validate (body .configuration , keep_sensitive = True )
317+ result = await validator .test_connection (body .configuration , body .source_path )
323318 if not result .success :
324319 raise errors .ValidationError (message = result .error )
325320 return empty (204 )
@@ -329,25 +324,23 @@ async def _test_connection(request: Request, validator: RCloneValidator) -> HTTP
329324 def validate (self ) -> BlueprintFactoryResponse :
330325 """Validate an RClone config."""
331326
332- async def _validate (request : Request , validator : RCloneValidator ) -> HTTPResponse :
333- if not request .json :
334- raise errors .ValidationError (message = "The request body is empty. Please provide a valid JSON object." )
335- if not isinstance (request .json , dict ):
336- raise errors .ValidationError (message = "The request body is not a valid JSON object." )
337- validator .validate (request .json , keep_sensitive = True )
327+ @validate (json = apispec .RCloneConfigValidate )
328+ async def _validate (
329+ request : Request , validator : RCloneValidator , body : apispec .RCloneConfigValidate
330+ ) -> HTTPResponse :
331+ validator .validate (body .root , keep_sensitive = True ) # type: ignore[arg-type]
338332 return empty (204 )
339333
340334 return "/storage_schema/validate" , ["POST" ], _validate
341335
342336 def obscure (self ) -> BlueprintFactoryResponse :
343337 """Obscure values in config."""
344338
345- async def _obscure (request : Request , validator : RCloneValidator ) -> JSONResponse :
346- if not request .json :
347- raise errors .ValidationError (message = "The request body is empty. Please provide a valid JSON object." )
348- if not isinstance (request .json , dict ):
349- raise errors .ValidationError (message = "The request body is not a valid JSON object." )
350- config = await validator .obscure_config (request .json )
351- return json (config )
339+ @validate (json = apispec .StorageSchemaObscurePostRequest )
340+ async def _obscure (
341+ request : Request , validator : RCloneValidator , body : apispec .StorageSchemaObscurePostRequest
342+ ) -> JSONResponse :
343+ config = await validator .obscure_config (body .configuration )
344+ return validated_json (apispec .RCloneConfigValidate , config )
352345
353346 return "/storage_schema/obscure" , ["POST" ], _obscure
0 commit comments