Conversation
…, metadata) Introduce PdfEngineRequest base class and concrete request types: FlattenPdfRequest, RotatePdfRequest, SplitPdfRequest, EncryptPdfRequest, ReadMetadataRequest, WriteMetadataRequest. Each maps to a standalone /forms/pdfengines/* route. Add PdfEngineBuilders static factory with fluent builder creation for each operation. Add ExecutePdfEngineAsync and ReadPdfMetadataAsync methods to GotenbergSharpClient. Include RotationAngle, PageRanges, and SplitMode value objects for domain validation. Add PdfEngineOperations example and README section.
|
Warning Rate limit exceeded
Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 14 minutes and 7 seconds. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (8)
📝 WalkthroughWalkthroughAdded standalone PDF engine operations support to GotenbergSharpClient with new request types (Flatten, Rotate, Split, Encrypt, ReadMetadata, WriteMetadata), builder factory classes, value objects for rotation angles and page ranges, API client methods, infrastructure constants, comprehensive tests, documentation, and a working example application. Changes
Sequence DiagramsequenceDiagram
participant Client
participant PdfEngineBuilders
participant PdfEngineBuilder
participant GotenbergSharpClient
participant Gotenberg as Gotenberg API
Client->>PdfEngineBuilders: PdfEngineBuilders.Rotate(angle)
PdfEngineBuilders->>PdfEngineBuilder: Create RotatePdfRequest
PdfEngineBuilder-->>Client: PdfEngineBuilder<RotatePdfRequest>
Client->>PdfEngineBuilder: WithPdfs(AssetBuilder)
PdfEngineBuilder->>PdfEngineBuilder: Set Assets
PdfEngineBuilder-->>Client: PdfEngineBuilder<RotatePdfRequest>
Client->>GotenbergSharpClient: ExecutePdfEngineAsync(builder)
GotenbergSharpClient->>PdfEngineBuilder: BuildAsync()
PdfEngineBuilder->>PdfEngineBuilder: Validate()
PdfEngineBuilder-->>GotenbergSharpClient: RotatePdfRequest
GotenbergSharpClient->>Gotenberg: POST /forms/pdfengines/rotate
Gotenberg-->>GotenbergSharpClient: Stream (rotated PDF bytes)
GotenbergSharpClient-->>Client: Task<Stream>
Estimated code review effort🎯 4 (Complex) | ⏱️ ~55 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (6)
src/Gotenberg.Sharp.Api.Client/Domain/Requests/FlattenPdfRequest.cs (1)
8-11: Consider adding XML documentation for consistency.Other new request types like
ReadMetadataRequestinclude XML documentation (/// <summary>). Adding similar documentation here would maintain consistency across the request types.📝 Suggested documentation
+/// <summary> +/// Flattens PDF form fields and annotations. +/// </summary> public sealed class FlattenPdfRequest : PdfEngineRequest { protected override string ApiPath => Constants.Gotenberg.PdfEngines.ApiPaths.Flatten; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Gotenberg.Sharp.Api.Client/Domain/Requests/FlattenPdfRequest.cs` around lines 8 - 11, Add XML documentation to FlattenPdfRequest to match other request types: add a /// <summary> describing that FlattenPdfRequest represents a request to the Gotenberg PDF engine to flatten PDFs, and document the overridden ApiPath property (or mention it in the class summary). Update the FlattenPdfRequest class (which inherits PdfEngineRequest and overrides ApiPath) to include these XML comments and keep wording consistent with ReadMetadataRequest and other request classes.src/Gotenberg.Sharp.Api.Client/Domain/Requests/EncryptPdfRequest.cs (1)
16-20: Use a consistent exception type for invalidUserPassword.Builder validation throws
ArgumentException, while request validation throwsInvalidOperationExceptionfor the same precondition. Aligning these improves API predictability.♻️ Proposed fix
if (string.IsNullOrWhiteSpace(this.UserPassword)) - throw new InvalidOperationException("User password is required for encryption."); + throw new ArgumentException("User password is required for encryption.", nameof(UserPassword));🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Gotenberg.Sharp.Api.Client/Domain/Requests/EncryptPdfRequest.cs` around lines 16 - 20, The EncryptPdfRequest.Validate method currently throws InvalidOperationException when UserPassword is null/whitespace; change this to throw ArgumentException to match the builder validation behavior. Locate the EncryptPdfRequest class and its Validate() override, replace the InvalidOperationException thrown for this.UserPassword with an ArgumentException (include a clear message and set the parameter name to "UserPassword"), so request-level validation uses the same exception type as the builder.examples/PdfEngineOperations/Program.cs (1)
29-58: Dispose response streams explicitly in the example flow.Streams from
ExecutePdfEngineAsyncandHtmlToPdfAsyncare currently left undisposed. Explicitusingavoids leaks and sets a better usage pattern for consumers.♻️ Proposed pattern
- var flattenResult = await sharpClient.ExecutePdfEngineAsync(...); - await SaveStream(flattenResult, destinationDirectory, "Flattened.pdf"); + using (var flattenResult = await sharpClient.ExecutePdfEngineAsync(...)) + await SaveStream(flattenResult, destinationDirectory, "Flattened.pdf"); - var stream = await client.HtmlToPdfAsync(builder); + using var stream = await client.HtmlToPdfAsync(builder);Also applies to: 74-85
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@examples/PdfEngineOperations/Program.cs` around lines 29 - 58, The example leaves response streams from ExecutePdfEngineAsync and ReadPdfMetadataAsync (and similar calls like HtmlToPdfAsync) undisposed; wrap each call's returned stream/result in a proper using (or await using for IAsyncDisposable) block so the stream is passed to SaveStream while guaranteed to be disposed afterwards; update the flattenResult, rotateResult, encryptResult, writeResult, metadataJson (and the similar block around HtmlToPdfAsync at the other location) to use using/await using around the call and then call SaveStream/read within that scope to prevent leaks and show the proper consumption pattern.test/GotenbergSharpClient.Tests/PdfEngineOperationsTests.cs (2)
218-222: Dispose the HTML->PDF stream inGenerateTestPdf.The source stream returned by
HtmlToPdfAsyncis copied but never disposed.♻️ Proposed fix
- var stream = await client.HtmlToPdfAsync(builder); + using var stream = await client.HtmlToPdfAsync(builder); using var ms = new MemoryStream(); await stream.CopyToAsync(ms); return ms.ToArray();🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/GotenbergSharpClient.Tests/PdfEngineOperationsTests.cs` around lines 218 - 222, The returned source stream from HtmlToPdfAsync is not disposed in GenerateTestPdf; update GenerateTestPdf to dispose the stream by wrapping the call in a using (or using var) — e.g. using var stream = await client.HtmlToPdfAsync(builder); — before copying to MemoryStream and returning ms.ToArray(); this ensures the Stream returned by HtmlToPdfAsync is properly disposed.
125-193: Dispose result streams in integration tests.Each
ExecutePdfEngineAsynccall returns a stream that should be disposed after assertions to avoid unnecessary resource retention in the suite.♻️ Proposed pattern
- var result = await client.ExecutePdfEngineAsync(builder); + using var result = await client.ExecutePdfEngineAsync(builder); result.Should().NotBeNull(); result.Length.Should().BeGreaterThan(0);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/GotenbergSharpClient.Tests/PdfEngineOperationsTests.cs` around lines 125 - 193, The tests call ExecutePdfEngineAsync(builder) which returns a stream (variable result) that is not disposed; update each test using ExecutePdfEngineAsync (e.g., the tests RotatePdf_Succeeds, EncryptPdf_Succeeds, WriteMetadata_Succeeds and any similar tests) to dispose the returned stream after assertions by wrapping the result in a using block or explicitly calling Dispose() (ensure assertions still run while the stream is open). Do not change ReadPdfMetadataAsync usage (it returns a string), and make the change for every occurrence of ExecutePdfEngineAsync(builder) in the test file.src/Gotenberg.Sharp.Api.Client/Domain/Builders/PdfEngineBuilder.cs (1)
98-107: Consider validatingspanfor empty/whitespace strings for consistency.The
Encryptmethod (line 114-115) validates thatuserPasswordis not null or whitespace, butSplitonly checks for null onspan. If an empty or whitespacespanvalue would cause an API error from Gotenberg, it would be better to fail fast here.🔧 Suggested validation enhancement
public static PdfEngineBuilder<SplitPdfRequest> Split(SplitMode mode, string span, bool unify = false) { + if (string.IsNullOrWhiteSpace(span)) + throw new ArgumentException("Span is required.", nameof(span)); + var request = new SplitPdfRequest { Mode = mode, - Span = span ?? throw new ArgumentNullException(nameof(span)), + Span = span, Unify = unify }; return new PdfEngineBuilder<SplitPdfRequest>(request); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Gotenberg.Sharp.Api.Client/Domain/Builders/PdfEngineBuilder.cs` around lines 98 - 107, The Split method currently only checks span for null; update validation to mirror Encrypt by treating empty or whitespace spans as invalid: in PdfEngineBuilder<SplitPdfRequest>.Split check string.IsNullOrWhiteSpace(span) and throw an ArgumentException (or ArgumentNullException with a clear message) if true, then assign Span when valid; reference Split, SplitPdfRequest and PdfEngineBuilder to locate the code to change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/Gotenberg.Sharp.Api.Client/Domain/Builders/PdfEngineBuilder.cs`:
- Around line 49-56: PdfEngineBuilder<TRequest>.WithPdfsAsync currently invokes
asyncAction immediately (via asyncAction(new AssetBuilder(this.Request.Assets
??= new AssetDictionary()))) which creates shared AssetDictionary and produces
Tasks that run concurrently at BuildAsync, causing race conditions; change
WithPdfsAsync so it does not call asyncAction eagerly but instead adds a
deferred wrapper to BuildTasks (e.g., a Func<Task> or a Task-returning lambda)
that creates the AssetBuilder from Request.Assets ??= new AssetDictionary() and
invokes asyncAction only when BuildAsync executes, or alternatively replace
AssetDictionary with a thread-safe collection; update related builders
(BaseChromiumBuilder, PdfConversionBuilder, BaseMergeBuilder) to the same
deferred-invocation pattern or thread-safe storage to eliminate concurrent
writes.
In `@src/Gotenberg.Sharp.Api.Client/Domain/Requests/WriteMetadataRequest.cs`:
- Around line 27-36: WriteMetadataRequest::ToHttpContent is incorrectly marking
JSON metadata as text/html; add an ApplicationJson constant to
Constants.HttpContent.MediaTypes and change the ContentType used in
WriteMetadataRequest.ToHttpContent from MediaTypes.TextHtml to
MediaTypes.ApplicationJson; also update MultiFormHeaderAttribute (which
currently defaults to TextHtml) to default to ApplicationJson so multipart form
parts carrying JSON metadata are correctly labeled.
In `@src/Gotenberg.Sharp.Api.Client/Domain/ValueObjects/PageRanges.cs`:
- Around line 26-55: The regex-only validation in PageRanges.Create permits
semantically invalid ranges like "0" or descending ranges "5-2"; after the
existing regex check, parse the trimmed string (ranges.Split(',')) and for each
segment (use the same trimming logic) validate that standalone numbers are >=1
and that hyphenated ranges parse to two integers both >=1 with start <= end,
throwing ArgumentException with a clear message if any check fails; keep
returning new PageRanges(trimmed) only after all semantic checks pass and
reference the ValidPattern, Create method, and PageRanges constructor/Value to
locate the changes.
---
Nitpick comments:
In `@examples/PdfEngineOperations/Program.cs`:
- Around line 29-58: The example leaves response streams from
ExecutePdfEngineAsync and ReadPdfMetadataAsync (and similar calls like
HtmlToPdfAsync) undisposed; wrap each call's returned stream/result in a proper
using (or await using for IAsyncDisposable) block so the stream is passed to
SaveStream while guaranteed to be disposed afterwards; update the flattenResult,
rotateResult, encryptResult, writeResult, metadataJson (and the similar block
around HtmlToPdfAsync at the other location) to use using/await using around the
call and then call SaveStream/read within that scope to prevent leaks and show
the proper consumption pattern.
In `@src/Gotenberg.Sharp.Api.Client/Domain/Builders/PdfEngineBuilder.cs`:
- Around line 98-107: The Split method currently only checks span for null;
update validation to mirror Encrypt by treating empty or whitespace spans as
invalid: in PdfEngineBuilder<SplitPdfRequest>.Split check
string.IsNullOrWhiteSpace(span) and throw an ArgumentException (or
ArgumentNullException with a clear message) if true, then assign Span when
valid; reference Split, SplitPdfRequest and PdfEngineBuilder to locate the code
to change.
In `@src/Gotenberg.Sharp.Api.Client/Domain/Requests/EncryptPdfRequest.cs`:
- Around line 16-20: The EncryptPdfRequest.Validate method currently throws
InvalidOperationException when UserPassword is null/whitespace; change this to
throw ArgumentException to match the builder validation behavior. Locate the
EncryptPdfRequest class and its Validate() override, replace the
InvalidOperationException thrown for this.UserPassword with an ArgumentException
(include a clear message and set the parameter name to "UserPassword"), so
request-level validation uses the same exception type as the builder.
In `@src/Gotenberg.Sharp.Api.Client/Domain/Requests/FlattenPdfRequest.cs`:
- Around line 8-11: Add XML documentation to FlattenPdfRequest to match other
request types: add a /// <summary> describing that FlattenPdfRequest represents
a request to the Gotenberg PDF engine to flatten PDFs, and document the
overridden ApiPath property (or mention it in the class summary). Update the
FlattenPdfRequest class (which inherits PdfEngineRequest and overrides ApiPath)
to include these XML comments and keep wording consistent with
ReadMetadataRequest and other request classes.
In `@test/GotenbergSharpClient.Tests/PdfEngineOperationsTests.cs`:
- Around line 218-222: The returned source stream from HtmlToPdfAsync is not
disposed in GenerateTestPdf; update GenerateTestPdf to dispose the stream by
wrapping the call in a using (or using var) — e.g. using var stream = await
client.HtmlToPdfAsync(builder); — before copying to MemoryStream and returning
ms.ToArray(); this ensures the Stream returned by HtmlToPdfAsync is properly
disposed.
- Around line 125-193: The tests call ExecutePdfEngineAsync(builder) which
returns a stream (variable result) that is not disposed; update each test using
ExecutePdfEngineAsync (e.g., the tests RotatePdf_Succeeds, EncryptPdf_Succeeds,
WriteMetadata_Succeeds and any similar tests) to dispose the returned stream
after assertions by wrapping the result in a using block or explicitly calling
Dispose() (ensure assertions still run while the stream is open). Do not change
ReadPdfMetadataAsync usage (it returns a string), and make the change for every
occurrence of ExecutePdfEngineAsync(builder) in the test file.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 3f3dcaaf-63e5-4c94-bf97-14704f2b1001
📒 Files selected for processing (17)
README.mdexamples/PdfEngineOperations/PdfEngineOperations.csprojexamples/PdfEngineOperations/Program.cssrc/Gotenberg.Sharp.Api.Client/Domain/Builders/PdfEngineBuilder.cssrc/Gotenberg.Sharp.Api.Client/Domain/Requests/EncryptPdfRequest.cssrc/Gotenberg.Sharp.Api.Client/Domain/Requests/FlattenPdfRequest.cssrc/Gotenberg.Sharp.Api.Client/Domain/Requests/PdfEngineRequest.cssrc/Gotenberg.Sharp.Api.Client/Domain/Requests/ReadMetadataRequest.cssrc/Gotenberg.Sharp.Api.Client/Domain/Requests/RotatePdfRequest.cssrc/Gotenberg.Sharp.Api.Client/Domain/Requests/SplitPdfRequest.cssrc/Gotenberg.Sharp.Api.Client/Domain/Requests/WriteMetadataRequest.cssrc/Gotenberg.Sharp.Api.Client/Domain/ValueObjects/PageRanges.cssrc/Gotenberg.Sharp.Api.Client/Domain/ValueObjects/RotationAngle.cssrc/Gotenberg.Sharp.Api.Client/Domain/ValueObjects/SplitMode.cssrc/Gotenberg.Sharp.Api.Client/GotenbergSharpClient.cssrc/Gotenberg.Sharp.Api.Client/Infrastructure/Constants.cstest/GotenbergSharpClient.Tests/PdfEngineOperationsTests.cs
| public PdfEngineBuilder<TRequest> WithPdfsAsync(Func<AssetBuilder, Task> asyncAction) | ||
| { | ||
| if (asyncAction == null) throw new ArgumentNullException(nameof(asyncAction)); | ||
|
|
||
| this.BuildTasks.Add(asyncAction(new AssetBuilder(this.Request.Assets ??= new AssetDictionary()))); | ||
|
|
||
| return this; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check if AssetDictionary has any thread-safety mechanisms or if other async builders use the same pattern
rg -n "BuildTasks\.Add" --type cs -C3Repository: ChangemakerStudios/GotenbergSharpApiClient
Length of output: 4647
🏁 Script executed:
#!/bin/bash
# Find BuildAsync method to see how tasks are executed
rg -n "BuildAsync" --type cs -A 10 | head -100Repository: ChangemakerStudios/GotenbergSharpApiClient
Length of output: 9226
🏁 Script executed:
#!/bin/bash
# Check the base class or interface that defines BuildTasks
rg -n "BuildTasks" --type cs -B 3 -A 1 | head -150Repository: ChangemakerStudios/GotenbergSharpApiClient
Length of output: 5301
🏁 Script executed:
#!/bin/bash
# Look at AssetDictionary to see if it has any thread-safety mechanisms
rg -n "class AssetDictionary" --type cs -A 10Repository: ChangemakerStudios/GotenbergSharpApiClient
Length of output: 1395
Potential race condition when multiple WithPdfsAsync calls are chained.
The asyncAction is invoked immediately (line 53), returning a Task that is added to BuildTasks. Multiple calls to WithPdfsAsync share the same AssetDictionary instance (via ??=). When BuildAsync() is called, all tasks execute concurrently through Task.WhenAll, causing concurrent writes to AssetDictionary, which extends Dictionary<string, ContentItem> (not thread-safe).
This pattern exists across the codebase (BaseChromiumBuilder, PdfConversionBuilder, BaseMergeBuilder, and others), suggesting either a widespread architectural issue or missing documentation on non-concurrent usage.
If chaining multiple WithPdfsAsync calls is supported, consider:
- Using
ConcurrentDictionary<string, ContentItem>forAssetDictionary - Adding documentation requiring sequential (non-concurrent) calls
- Deferring
asyncActioninvocation by wrapping in a lambda, only invoking duringBuildAsync()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/Gotenberg.Sharp.Api.Client/Domain/Builders/PdfEngineBuilder.cs` around
lines 49 - 56, PdfEngineBuilder<TRequest>.WithPdfsAsync currently invokes
asyncAction immediately (via asyncAction(new AssetBuilder(this.Request.Assets
??= new AssetDictionary()))) which creates shared AssetDictionary and produces
Tasks that run concurrently at BuildAsync, causing race conditions; change
WithPdfsAsync so it does not call asyncAction eagerly but instead adds a
deferred wrapper to BuildTasks (e.g., a Func<Task> or a Task-returning lambda)
that creates the AssetBuilder from Request.Assets ??= new AssetDictionary() and
invokes asyncAction only when BuildAsync executes, or alternatively replace
AssetDictionary with a thread-safe collection; update related builders
(BaseChromiumBuilder, PdfConversionBuilder, BaseMergeBuilder) to the same
deferred-invocation pattern or thread-safe storage to eliminate concurrent
writes.
| protected override IEnumerable<HttpContent> ToHttpContent() | ||
| { | ||
| var metadataContent = new StringContent(this.Metadata!.ToString()); | ||
| metadataContent.Headers.ContentType = new MediaTypeHeaderValue(Constants.HttpContent.MediaTypes.TextHtml); | ||
| metadataContent.Headers.ContentDisposition = | ||
| new ContentDispositionHeaderValue(Constants.HttpContent.Disposition.Types.FormData) | ||
| { | ||
| Name = "metadata" | ||
| }; | ||
| yield return metadataContent; |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
Gotenberg PDF engine metadata write endpoint content type requirement
💡 Result:
The Gotenberg PDF engine metadata write endpoint is POST /forms/pdfengines/metadata/write. It requires the Content-Type header to be set to multipart/form-data, as all Gotenberg routes are designed to accept multipart/form-data requests containing form fields and files. Required parts: - Form files: One or more PDF files (named arbitrarily, e.g., files=@pdf.pdf). - Form field: metadata (JSON string, required; e.g., --form 'metadata={"CreateDate": "2025:05:16 22:03:20+02:00"}'). Example from GitHub discussion #1189: curl --request POST http://localhost:3000/forms/pdfengines/metadata/write --form files=@pdfa.pdf --form 'metadata={"CreateDate": "2025:05:16 22:03:20+02:00"}' -o my.pdf Official docs confirm this is a multipart/form-data route (Routes page). Not all metadata keys are writable (see ExifTool XMP PDF tags). Writing metadata may break PDF/A compliance.
Citations:
- 1: Question on wrong createDate in metadata after PDF/A creation gotenberg/gotenberg#1189
- 2: https://gotenberg.dev/docs/routes
- 3: feat(ExifTool): read/write metadata gotenberg/gotenberg#803
🏁 Script executed:
# Search for other StringContent usages with TextHtml
rg "MediaTypes\.TextHtml" --type cs
# Search for other StringContent in request classes
rg "StringContent" --type cs -A 3
# Check for WriteMetadataRequest usage/tests
fd -e "*.cs" -x rg -l "WriteMetadataRequest"Repository: ChangemakerStudios/GotenbergSharpApiClient
Length of output: 2613
🏁 Script executed:
# Check MultiFormHeaderAttribute context
cat -n src/Gotenberg.Sharp.Api.Client/Infrastructure/MultiFormHeaderAttribute.cs | head -30
# Check if there are tests for WriteMetadataRequest
rg -l "WriteMetadataRequest" --type cs
# Look at the full context of BuildRequestBase.CreateFormDataItem
cat -n src/Gotenberg.Sharp.Api.Client/Domain/Requests/BuildRequestBase.cs | grep -A 10 "CreateFormDataItem"
# Check Constants.HttpContent to see what MediaTypes are available
rg "MediaTypes\." --type cs | grep -E "(class|const|=)" | head -20Repository: ChangemakerStudios/GotenbergSharpApiClient
Length of output: 2968
🏁 Script executed:
# Find Constants file with MediaTypes
fd "Constants.cs" -o fd "Constants" --type f | head -10
# Search for MediaTypes definition
rg "class MediaTypes|namespace.*Constants" --type cs -B 2 -A 15 | head -50
# Check the full Constants definition
rg -B 5 "TextHtml|ApplicationJson" --type cs | grep -E "(const|=|public)" | head -20
# Look at the test file for WriteMetadataRequest
cat test/GotenbergSharpClient.Tests/PdfEngineOperationsTests.cs | grep -A 30 "WriteMetadata"Repository: ChangemakerStudios/GotenbergSharpApiClient
Length of output: 5493
Incorrect ContentType for JSON metadata.
The metadata content is JSON (from JObject.ToString()), but ContentType is set to "text/html" instead of "application/json". While integration tests pass (suggesting Gotenberg tolerates this), the content type is semantically incorrect for JSON. Add an ApplicationJson constant to MediaTypes and use it here:
var metadataContent = new StringContent(this.Metadata!.ToString());
-metadataContent.Headers.ContentType = new MediaTypeHeaderValue(Constants.HttpContent.MediaTypes.TextHtml);
+metadataContent.Headers.ContentType = new MediaTypeHeaderValue(Constants.HttpContent.MediaTypes.ApplicationJson);Note: MultiFormHeaderAttribute also defaults to TextHtml and should be updated similarly.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/Gotenberg.Sharp.Api.Client/Domain/Requests/WriteMetadataRequest.cs`
around lines 27 - 36, WriteMetadataRequest::ToHttpContent is incorrectly marking
JSON metadata as text/html; add an ApplicationJson constant to
Constants.HttpContent.MediaTypes and change the ContentType used in
WriteMetadataRequest.ToHttpContent from MediaTypes.TextHtml to
MediaTypes.ApplicationJson; also update MultiFormHeaderAttribute (which
currently defaults to TextHtml) to default to ApplicationJson so multipart form
parts carrying JSON metadata are correctly labeled.
Summary
PdfEngineRequestbase class with concrete types:FlattenPdfRequest,RotatePdfRequest,SplitPdfRequest,EncryptPdfRequest,ReadMetadataRequest,WriteMetadataRequestPdfEngineBuildersstatic factory with fluent creation:Flatten(),Rotate(90),Split(mode, span),Encrypt(user, owner),ReadMetadata(),WriteMetadata(dict)ExecutePdfEngineAsyncandReadPdfMetadataAsync(returns JSON string) toGotenbergSharpClient/forms/pdfengines/flatten,/rotate,/split,/encrypt,/metadata/read,/metadata/write,/watermark,/stampTest plan
Summary by CodeRabbit
New Features
Documentation