Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add library-level enforcement of component field limits #1065

Merged
merged 17 commits into from
Apr 18, 2022
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions discord/components.py
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,12 @@ def __init__(
emoji: Optional[Union[str, Emoji, PartialEmoji]] = None,
default: bool = False,
) -> None:
if len(label) > 100:
raise ValueError("label must be 100 characters or fewer")
if value is not MISSING and len(value) > 100:
raise ValueError("value must be 100 characters or fewer")
if description is not None and len(description) > 100:
raise ValueError("description must be 100 characters or fewer")
self.label = label
self.value = label if value is MISSING else value
self.description = description
Expand Down
11 changes: 9 additions & 2 deletions discord/ui/button.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,14 @@ def __init__(
row: Optional[int] = None,
):
super().__init__()
if len(label) > 80:
raise ValueError("label must be 80 characters or fewer")
if custom_id is not None and len(custom_id) > 100:
raise ValueError("custom_id must be 100 characters or fewer")
if custom_id is not None and url is not None:
raise TypeError("cannot mix both url and custom_id with Button")

if not (isinstance(custom_id, str) or custom_id is None):
if not isinstance(custom_id, str) and custom_id is not None:
raise TypeError(f"expected custom_id to be str, not {custom_id.__class__.__name__}")

self._provided_custom_id = custom_id is not None
Expand Down Expand Up @@ -149,7 +153,8 @@ def custom_id(self) -> Optional[str]:
def custom_id(self, value: Optional[str]):
if value is not None and not isinstance(value, str):
raise TypeError("custom_id must be None or str")

if len(value) > 100:
raise ValueError("custom_id must be 100 characters or fewer")
self._underlying.custom_id = value

@property
Expand Down Expand Up @@ -179,6 +184,8 @@ def label(self) -> Optional[str]:

@label.setter
def label(self, value: Optional[str]):
if len(value) > 80:
raise ValueError("label must be 80 characters or fewer")
self._underlying.label = str(value) if value is not None else value

@property
Expand Down
42 changes: 31 additions & 11 deletions discord/ui/input_text.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def __init__(
self,
*,
style: InputTextStyle = InputTextStyle.short,
custom_id: str = MISSING,
custom_id: Optional[str] = None,
label: str,
placeholder: Optional[str] = None,
min_length: Optional[int] = None,
Expand All @@ -63,9 +63,19 @@ def __init__(
row: Optional[int] = None,
):
super().__init__()
custom_id = os.urandom(16).hex() if custom_id is MISSING else custom_id
if not (isinstance(custom_id, str) or custom_id is None):
if len(label) > 45:
raise ValueError("label must be 45 characters or fewer")
if 0 <= min_length <= 4000:
krittick marked this conversation as resolved.
Show resolved Hide resolved
raise ValueError("min_length must be between 0 and 4000")
if 0 < max_length <= 4000:
krittick marked this conversation as resolved.
Show resolved Hide resolved
raise ValueError("max_length must be between 1 and 4000")
if len(value) > 4000:
raise ValueError("value must be 4000 characters or fewer")
if len(placeholder) > 100:
raise ValueError("placeholder must be 100 characters or fewer")
if not isinstance(custom_id, str) and custom_id is not None:
raise TypeError(f"expected custom_id to be str, not {custom_id.__class__.__name__}")
custom_id = os.urandom(16).hex() if custom_id is None else custom_id

self._underlying = InputTextComponent._raw_construct(
type=ComponentType.input_text,
Expand Down Expand Up @@ -94,7 +104,7 @@ def style(self) -> InputTextStyle:
@style.setter
def style(self, value: InputTextStyle):
if not isinstance(value, InputTextStyle):
raise TypeError(f"style must be of type InputTextStyle not {value.__class__}")
raise TypeError(f"style must be of type InputTextStyle not {value.__class__.__name__}")
self._underlying.style = value

@property
Expand All @@ -105,7 +115,7 @@ def custom_id(self) -> str:
@custom_id.setter
def custom_id(self, value: str):
if not isinstance(value, str):
raise TypeError(f"custom_id must be None or str not {value.__class__}")
raise TypeError(f"custom_id must be None or str not {value.__class__.__name__}")
self._underlying.custom_id = value

@property
Expand All @@ -115,8 +125,10 @@ def label(self) -> str:

@label.setter
def label(self, value: str):
if len(value) > 45:
raise ValueError("label must be 45 characters or fewer")
if not isinstance(value, str):
raise TypeError(f"label should be str not {value.__class__}")
raise TypeError(f"label should be str not {value.__class__.__name__}")
self._underlying.label = value

@property
Expand All @@ -127,7 +139,9 @@ def placeholder(self) -> Optional[str]:
@placeholder.setter
def placeholder(self, value: Optional[str]):
if value and not isinstance(value, str):
raise TypeError(f"placeholder must be None or str not {value.__class__}") # type: ignore
raise TypeError(f"placeholder must be None or str not {value.__class__.__name__}") # type: ignore
if len(value) > 100:
raise ValueError("placeholder must be 100 characters or fewer")
self._underlying.placeholder = value

@property
Expand All @@ -138,7 +152,9 @@ def min_length(self) -> Optional[int]:
@min_length.setter
def min_length(self, value: Optional[int]):
if value and not isinstance(value, int):
raise TypeError(f"min_length must be None or int not {value.__class__}") # type: ignore
raise TypeError(f"min_length must be None or int not {value.__class__.__name__}") # type: ignore
if 0 <= value <= 4000:
raise ValueError("min_length must be between 0 and 4000")
krittick marked this conversation as resolved.
Show resolved Hide resolved
self._underlying.min_length = value

@property
Expand All @@ -149,7 +165,9 @@ def max_length(self) -> Optional[int]:
@max_length.setter
def max_length(self, value: Optional[int]):
if value and not isinstance(value, int):
raise TypeError(f"min_length must be None or int not {value.__class__}") # type: ignore
raise TypeError(f"min_length must be None or int not {value.__class__.__name__}") # type: ignore
if 0 < value <= 4000:
raise ValueError("max_length must be between 1 and 4000")
self._underlying.max_length = value

@property
Expand All @@ -160,7 +178,7 @@ def required(self) -> Optional[bool]:
@required.setter
def required(self, value: Optional[bool]):
if not isinstance(value, bool):
raise TypeError(f"required must be bool not {value.__class__}") # type: ignore
raise TypeError(f"required must be bool not {value.__class__.__name__}") # type: ignore
self._underlying.required = bool(value)

@property
Expand All @@ -174,7 +192,9 @@ def value(self) -> Optional[str]:
@value.setter
def value(self, value: Optional[str]):
if value and not isinstance(value, str):
raise TypeError(f"value must be None or str not {value.__class__}") # type: ignore
raise TypeError(f"value must be None or str not {value.__class__.__name__}") # type: ignore
if len(value) > 4000:
raise ValueError("value must be 4000 characters or fewer")
self._underlying.value = value

@property
Expand Down
43 changes: 35 additions & 8 deletions discord/ui/modal.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
import asyncio
import os
import sys
from itertools import groupby
import traceback
from itertools import groupby
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple

from .input_text import InputText
Expand Down Expand Up @@ -37,19 +37,46 @@ class Modal:
"""

def __init__(self, title: str, custom_id: Optional[str] = None) -> None:
if not (isinstance(custom_id, str) or custom_id is None):
if not isinstance(custom_id, str) and custom_id is not None:
raise TypeError(f"expected custom_id to be str, not {custom_id.__class__.__name__}")

self.custom_id = custom_id or os.urandom(16).hex()
self.title = title
self._custom_id: Optional[str] = custom_id or os.urandom(16).hex()
if len(title) > 45:
raise ValueError("title must be 45 characters or fewer")
self._title = title
self.children: List[InputText] = []
self.__weights = _ModalWeights(self.children)
self._weights = _ModalWeights(self.children)
loop = asyncio.get_running_loop()
self._stopped: asyncio.Future[bool] = loop.create_future()

@property
def title(self) -> str:
"""The title of the modal dialog."""
return self._title

@title.setter
def title(self, value: str):
if len(value) > 45:
raise ValueError("title must be 45 characters or fewer")
if not isinstance(value, str):
raise TypeError(f"expected title to be str, not {value.__class__.__name__}")
self._title = value

@property
def custom_id(self) -> str:
"""The ID of the modal dialog that gets received during an interaction."""
return self._custom_id

@custom_id.setter
def custom_id(self, value: str):
if not isinstance(value, str):
raise TypeError(f"expected custom_id to be str, not {value.__class__.__name__}")
if len(value) > 100:
raise ValueError("custom_id must be 100 characters or fewer")
self._custom_id = value

async def callback(self, interaction: Interaction):
"""|coro|

The coroutine that is called when the modal dialog is submitted.
Should be overridden to handle the values submitted by the user.

Expand Down Expand Up @@ -95,7 +122,7 @@ def add_item(self, item: InputText):
if not isinstance(item, InputText):
raise TypeError(f"expected InputText not {item.__class__!r}")

self.__weights.add_item(item)
self._weights.add_item(item)
self.children.append(item)

def remove_item(self, item: InputText):
Expand Down
22 changes: 17 additions & 5 deletions discord/ui/select.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ class Select(Item[V]):
def __init__(
self,
*,
custom_id: str = None,
custom_id: Optional[str] = None,
placeholder: Optional[str] = None,
min_values: int = 1,
max_values: int = 1,
Expand All @@ -106,8 +106,13 @@ def __init__(
) -> None:
super().__init__()
self._selected_values: List[str] = []

if not (isinstance(custom_id, str) or custom_id is None):
if min_values < 0 or min_values > 25:
raise ValueError("min_values must be between 0 and 25")
if max_values < 1 or max_values > 25:
raise ValueError("max_values must be between 1 and 25")
if len(placeholder) > 150:
raise ValueError("placeholder must be 150 characters or fewer")
if not isinstance(custom_id, str) and custom_id is not None:
raise TypeError(f"expected custom_id to be str, not {custom_id.__class__.__name__}")

self._provided_custom_id = custom_id is not None
Expand All @@ -133,7 +138,8 @@ def custom_id(self) -> str:
def custom_id(self, value: str):
if not isinstance(value, str):
raise TypeError("custom_id must be None or str")

if len(value) > 100:
raise ValueError("custom_id must be 100 characters or fewer")
self._underlying.custom_id = value

@property
Expand All @@ -143,6 +149,8 @@ def placeholder(self) -> Optional[str]:

@placeholder.setter
def placeholder(self, value: Optional[str]):
if len(value) > 150:
raise ValueError("placeholder must be 150 characters or fewer")
if value is not None and not isinstance(value, str):
raise TypeError("placeholder must be None or str")

Expand All @@ -155,6 +163,8 @@ def min_values(self) -> int:

@min_values.setter
def min_values(self, value: int):
if value < 0 or value > 25:
raise ValueError("min_values must be between 0 and 25")
self._underlying.min_values = int(value)

@property
Expand All @@ -164,6 +174,8 @@ def max_values(self) -> int:

@max_values.setter
def max_values(self, value: int):
if value < 1 or value > 25:
raise ValueError("max_values must be between 1 and 25")
self._underlying.max_values = int(value)

@property
Expand Down Expand Up @@ -297,7 +309,7 @@ def is_dispatchable(self) -> bool:
def select(
*,
placeholder: Optional[str] = None,
custom_id: str = None,
custom_id: Optional[str] = None,
min_values: int = 1,
max_values: int = 1,
options: List[SelectOption] = MISSING,
Expand Down