Files

303 lines
9.9 KiB
Python

# Copyright 2023 Declan Teevan
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import logging
import traceback
from typing import Type
from google.protobuf import empty_pb2
from grpc import RpcContext, StatusCode
from comment_service.models.exceptions import ServiceException
from comment_service.models.service import CommentRepository, Comment, CommentCreate, CommentUpdate
from comment_service.models.proto import comment_pb2, comment_pb2_grpc
class CommentServicer(comment_pb2_grpc.CommentServiceServicer):
"""Contains definitions for the service's RPC methods.
Requests are converted from protobuf to business model
form, then directed to the service repository, where the
response is then translated back to protobuf.
Attributes:
_svc_repo (Type[CommentRepository]): The highest level service repository.
"""
def __init__(self, svc_repo: Type[CommentRepository]) -> None:
self._svc_repo = svc_repo
def _apply_error(self, context: RpcContext, code: StatusCode, msg: str) -> None:
"""Apply an error to a given RPC context.
Args:
context (grpc.RpcContext): The context to apply the error to.
code (grpc.StatusCode): The gRPC status code.
msg (str): The error details.
"""
context.set_code(code)
context.set_details(msg)
def _apply_unknown_error(self, context: RpcContext) -> None:
"""Apply a de facto error fallback message.
Args:
context (grpc.RpcContext): The context to apply the error to.
"""
self._apply_error(context, StatusCode.UNKNOWN, "unknown error occured")
async def CreateComment(self, request: comment_pb2.CreateCommentRequest, context: RpcContext) -> comment_pb2.Comment:
"""CreateComment RPC Call
Args:
request (comment_pb2.CreateCommentRequest): The request parameters.
context (grpc.RpcContext): The context of the RPC call.
Returns:
comment_pb2.Comment: With a succesful comment creation.
"""
# vaLidate the request inputs
if request.post_id == "":
self._apply_error(
context,
code=StatusCode.INVALID_ARGUMENT,
msg="post not provided"
)
return
if request.author_id == "":
self._apply_error(
context,
code=StatusCode.INVALID_ARGUMENT,
msg="author not provided"
)
return
if request.data == None:
self._apply_error(
context,
code=StatusCode.INVALID_ARGUMENT,
msg="malformed request"
)
return
if request.data.message == "":
self._apply_error(
context,
code=StatusCode.INVALID_ARGUMENT,
msg="comment message not provided"
)
return
# convert to service model from protobuf
try:
data = CommentCreate.from_protobuf(request)
comment = await self._svc_repo.create_comment(data)
except ServiceException as err:
err.apply_to_rpc(context)
return
except Exception:
logging.error(traceback.format_exc())
self._apply_unknown_error(context)
return
# convert comment to protobuf form
return Comment.to_protobuf(comment)
async def UpdateComment(self, request: comment_pb2.UpdateCommentRequest, context: RpcContext) -> comment_pb2.Comment:
"""UpdateComment RPC Call
Args:
request (comment_pb2.UpdateCommentRequest): The request parameters.
context (grpc.RpcContext): The context of the RPC call.
Returns:
comment_pb2.Comment: The updated comment details (if succesfully updated).
"""
# vaLidate the request inputs
if request.id == "":
self._apply_error(
context,
code=StatusCode.INVALID_ARGUMENT,
msg="comment not provided"
)
return
elif not request.id.isnumeric():
self._apply_error(
context,
code=StatusCode.INVALID_ARGUMENT,
msg="invalid comment id provided"
)
return
if request.data == None:
self._apply_error(
context,
code=StatusCode.INVALID_ARGUMENT,
msg="malformed request"
)
return
if request.data.message == "":
self._apply_error(
context,
code=StatusCode.INVALID_ARGUMENT,
msg="comment message not provided"
)
return
# convert to service model from protobuf
try:
comment_id = int(request.id)
data = CommentUpdate.from_protobuf(request)
comment = await self._svc_repo.update_comment(comment_id, data)
except ServiceException as err:
err.apply_to_rpc(context)
return
except Exception:
logging.error(traceback.format_exc())
self._apply_unknown_error(context)
return
# convert comment to protobuf form
return Comment.to_protobuf(comment)
async def DeleteComment(self, request: comment_pb2.DeleteCommentRequest, context: RpcContext) -> empty_pb2.Empty:
"""DeleteComment RPC Call
Args:
request (comment_pb2.DeleteCommentRequest): The request parameters.
context (grpc.RpcContext): The context of the RPC call.
Returns:
empty_pb2.Empty: Empty protobuf response (in effect returns None).
"""
# vaLidate the request inputs
if request.id == "":
self._apply_error(
context,
code=StatusCode.INVALID_ARGUMENT,
msg="comment not provided"
)
return
if not request.id.isnumeric():
self._apply_error(
context,
code=StatusCode.INVALID_ARGUMENT,
msg="invalid comment id provided"
)
return
# attempt to delete the comment
try:
comment_id = int(request.id)
await self._svc_repo.delete_comment(comment_id)
except ServiceException as err:
err.apply_to_rpc(context)
return
except Exception:
logging.error(traceback.format_exc())
self._apply_unknown_error(context)
return
return empty_pb2.Empty()
async def GetComment(self, request: comment_pb2.GetCommentRequest, context: RpcContext) -> comment_pb2.PostComments:
"""GetComment RPC Call
Returns a comment by comment id.
Args:
request (comment_pb2.GetCommentRequest): The request parameters.
context (grpc.RpcContext): The context of the RPC call.
Returns:
comment_pb2.Comment: The located comment
"""
# vaLidate the request inputs
if request.id == "":
self._apply_error(
context,
code=StatusCode.INVALID_ARGUMENT,
msg="comment id not provided"
)
return
if not request.id.isnumeric():
self._apply_error(
context,
code=StatusCode.INVALID_ARGUMENT,
msg="invalid comment id provided"
)
return
# attempt to get the comment
try:
comment = await self._svc_repo.get_comment(int(request.id))
except ServiceException as err:
err.apply_to_rpc(context)
return
except Exception:
logging.error(traceback.format_exc())
self._apply_unknown_error(context)
return
return Comment.to_protobuf(comment)
async def GetPostComments(self, request: comment_pb2.GetPostCommentsRequest, context: RpcContext) -> comment_pb2.PostComments:
"""GetPostComments RPC Call
Returns a list of comments that a post has.
TODO:
Implement pagination (?after=comment_id or some effect)
to return more comments from a post.
Args:
request (comment_pb2.UpdateCommentRequest): The request parameters.
context (grpc.RpcContext): The context of the RPC call.
Returns:
comment_pb2.PostComments: containing a list of the post's comments
"""
# vaLidate the request inputs
if request.post_id == "":
self._apply_error(
context,
code=StatusCode.INVALID_ARGUMENT,
msg="post id not provided"
)
return
# attempt to get the comments
try:
comments = await self._svc_repo.get_post_comments(request.post_id)
except ServiceException as err:
err.apply_to_rpc(context)
return
except Exception:
logging.error(traceback.format_exc())
self._apply_unknown_error(context)
return
# convert to protobuf
return comment_pb2.PostComments(comments=[Comment.to_protobuf(comment) for comment in comments])