Skip to content

Commit 17b02ac

Browse files
pdf: Apply regex pattern match to page_margin
Assisted-by: Codex
1 parent ca21c8f commit 17b02ac

3 files changed

Lines changed: 109 additions & 17 deletions

File tree

src/pdfrest/models/_internal.py

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -253,17 +253,7 @@ def _validate_output_language(value: str) -> str:
253253
return trimmed
254254

255255

256-
_PAGE_MARGIN_PATTERN = re.compile(r"^(?:\d+(?:\.\d+)?)(?:mm|in)$")
257-
258-
259-
def _validate_page_margin(value: str | None) -> str | None:
260-
if value is None:
261-
return None
262-
trimmed = value.strip()
263-
if not trimmed or not _PAGE_MARGIN_PATTERN.fullmatch(trimmed):
264-
msg = "page_margin must be a number followed by 'in' or 'mm'."
265-
raise ValueError(msg)
266-
return trimmed
256+
_PAGE_MARGIN_REGEX = r"^(?:\d+(?:\.\d+)?)(?:mm|in)$"
267257

268258

269259
class UploadURLs(BaseModel):
@@ -784,8 +774,11 @@ class ConvertHtmlToPdfPayload(BaseModel):
784774
] = None
785775
page_margin: Annotated[
786776
str | None,
787-
Field(serialization_alias="page_margin", default=None),
788-
AfterValidator(_validate_page_margin),
777+
Field(
778+
serialization_alias="page_margin",
779+
pattern=_PAGE_MARGIN_REGEX,
780+
default=None,
781+
),
789782
] = None
790783
page_orientation: Annotated[
791784
HtmlPageOrientation | None,
@@ -825,8 +818,11 @@ class ConvertUrlToPdfPayload(BaseModel):
825818
] = None
826819
page_margin: Annotated[
827820
str | None,
828-
Field(serialization_alias="page_margin", default=None),
829-
AfterValidator(_validate_page_margin),
821+
Field(
822+
serialization_alias="page_margin",
823+
pattern=_PAGE_MARGIN_REGEX,
824+
default=None,
825+
),
830826
] = None
831827
page_orientation: Annotated[
832828
HtmlPageOrientation | None,

tests/test_convert_html_to_pdf.py

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ def test_convert_html_to_pdf_validation_errors() -> None:
154154
}
155155
)
156156

157-
with pytest.raises(ValidationError, match="page_margin must be a number"):
157+
with pytest.raises(ValidationError, match="String should match pattern"):
158158
ConvertHtmlToPdfPayload.model_validate(
159159
{
160160
"files": [
@@ -169,6 +169,63 @@ def test_convert_html_to_pdf_validation_errors() -> None:
169169
)
170170

171171

172+
@pytest.mark.parametrize(
173+
"page_margin",
174+
[
175+
pytest.param("8mm", id="whole-millimeters"),
176+
pytest.param("2.5in", id="decimal-inches"),
177+
pytest.param("10.25mm", id="long-decimal-millimeters"),
178+
pytest.param("0in", id="zero-inches"),
179+
],
180+
)
181+
def test_convert_html_to_pdf_page_margin_accepts_documented_values(
182+
page_margin: str,
183+
) -> None:
184+
payload = ConvertHtmlToPdfPayload.model_validate(
185+
{
186+
"files": [
187+
make_source_file(
188+
str(PdfRestFileID.generate()),
189+
"text/html",
190+
"example.html",
191+
)
192+
],
193+
"page_margin": page_margin,
194+
}
195+
)
196+
197+
assert payload.page_margin == page_margin
198+
199+
200+
@pytest.mark.parametrize(
201+
"page_margin",
202+
[
203+
pytest.param("8", id="missing-unit"),
204+
pytest.param("mm", id="missing-number"),
205+
pytest.param("2.5 in", id="embedded-space"),
206+
pytest.param("8MM", id="uppercase-unit"),
207+
pytest.param(" 8mm", id="leading-space"),
208+
pytest.param("8mm ", id="trailing-space"),
209+
],
210+
)
211+
def test_convert_html_to_pdf_page_margin_rejects_invalid_values(
212+
page_margin: str,
213+
) -> None:
214+
with pytest.raises(ValidationError, match="String should match pattern"):
215+
ConvertHtmlToPdfPayload.model_validate(
216+
{
217+
"files": [
218+
make_source_file(
219+
str(PdfRestFileID.generate()),
220+
"text/html",
221+
"example.html",
222+
)
223+
],
224+
"page_margin": page_margin,
225+
}
226+
)
227+
228+
172229
@pytest.mark.asyncio
173230
async def test_async_convert_html_to_pdf_success(
174231
monkeypatch: pytest.MonkeyPatch,

tests/test_convert_url_to_pdf.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,12 +79,51 @@ def test_convert_url_to_pdf_validation_errors() -> None:
7979
{"url": ["https://example.com/one", "https://example.com/two"]}
8080
)
8181

82-
with pytest.raises(ValidationError, match="page_margin must be a number"):
82+
with pytest.raises(ValidationError, match="String should match pattern"):
8383
ConvertUrlToPdfPayload.model_validate(
8484
{"url": "https://example.com", "page_margin": "mm"}
8585
)
8686

8787

88+
@pytest.mark.parametrize(
89+
"page_margin",
90+
[
91+
pytest.param("8mm", id="whole-millimeters"),
92+
pytest.param("2.5in", id="decimal-inches"),
93+
pytest.param("10.25mm", id="long-decimal-millimeters"),
94+
pytest.param("0in", id="zero-inches"),
95+
],
96+
)
97+
def test_convert_url_to_pdf_page_margin_accepts_documented_values(
98+
page_margin: str,
99+
) -> None:
100+
payload = ConvertUrlToPdfPayload.model_validate(
101+
{"url": "https://example.com/page", "page_margin": page_margin}
102+
)
103+
104+
assert payload.page_margin == page_margin
105+
106+
107+
@pytest.mark.parametrize(
108+
"page_margin",
109+
[
110+
pytest.param("8", id="missing-unit"),
111+
pytest.param("mm", id="missing-number"),
112+
pytest.param("2.5 in", id="embedded-space"),
113+
pytest.param("8MM", id="uppercase-unit"),
114+
pytest.param(" 8mm", id="leading-space"),
115+
pytest.param("8mm ", id="trailing-space"),
116+
],
117+
)
118+
def test_convert_url_to_pdf_page_margin_rejects_invalid_values(
119+
page_margin: str,
120+
) -> None:
121+
with pytest.raises(ValidationError, match="String should match pattern"):
122+
ConvertUrlToPdfPayload.model_validate(
123+
{"url": "https://example.com/page", "page_margin": page_margin}
124+
)
125+
126+
88127
def test_convert_url_to_pdf_request_customization(
89128
monkeypatch: pytest.MonkeyPatch,
90129
) -> None:

0 commit comments

Comments
 (0)