Skip to content

Commit 98a3d0b

Browse files
elucidsoftclaude
andcommitted
feat: implement complete Ruby SDK v0.1.0
- Full Ruby 3.1+ SDK with keyword arguments and block-based configuration - API version support: v1 (sync binary), v2 (async Job) - Conversion API: url_to_pdf, url_to_image, html_to_pdf, html_to_image, template_to_pdf, template_to_image, docx_to_pdf, docx_to_html, pdf_to_docx, merge_pdfs - Data Management API: list/get jobs, assets, storage; add/delete storage; get_account, get_status, list/get templates - Utility: wait_for_job (polling), download_job_result, HtmlUtil.encode_html - HTTP transport with retry (exponential backoff+jitter), multipart upload, redirect following for presigned S3 URLs - Type system: frozen constant modules, OptionBase DSL with field declarations, response classes with from_hash deserialization - Error hierarchy: Error < {ConfigError, ValidationError, NetworkError, TimeoutError, ApiError < {AuthError, RateLimitError}} - YARD doc comments on all public classes and methods - RSpec test suite: >97% coverage across unit and integration specs - RuboCop linting, .yardopts for YARD generation - Comprehensive README with examples for all methods - CHANGELOG for v0.1.0 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 21822eb commit 98a3d0b

67 files changed

Lines changed: 5530 additions & 15 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.rubocop.yml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,27 @@ Metrics/BlockLength:
1717
- "spec/**/*"
1818

1919
Metrics/MethodLength:
20+
Max: 15
21+
CountAsOne:
22+
- hash
23+
- heredoc
2024
Exclude:
2125
- "spec/**/*"
2226

2327
Metrics/ModuleLength:
28+
Max: 110
2429
Exclude:
2530
- "spec/**/*"
31+
32+
Metrics/ClassLength:
33+
Exclude:
34+
- "lib/cloudlayerio/http/transport.rb"
35+
36+
# API method names match the CloudLayer SDK convention across all languages
37+
Naming/AccessorMethodName:
38+
Exclude:
39+
- "lib/cloudlayerio/api/**/*"
40+
41+
Naming/PredicateMethod:
42+
Exclude:
43+
- "lib/cloudlayerio/api/**/*"

CHANGELOG.md

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,42 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8-
## [Unreleased]
8+
## [0.1.0] - 2026-03-17
9+
10+
### Added
11+
12+
- Client configuration with keyword arguments and block-based setup
13+
- API version support (v1 sync binary, v2 async Job)
14+
- **Conversion methods:**
15+
- `url_to_pdf` / `url_to_image` — convert URLs to PDF or image
16+
- `html_to_pdf` / `html_to_image` — convert Base64-encoded HTML
17+
- `template_to_pdf` / `template_to_image` — render server-side templates
18+
- `docx_to_pdf` / `docx_to_html` — convert DOCX documents (multipart upload)
19+
- `pdf_to_docx` — convert PDF to DOCX (multipart upload)
20+
- `merge_pdfs` — merge multiple PDFs from URLs
21+
- **Data management:**
22+
- `list_jobs` / `get_job` — query conversion jobs
23+
- `list_assets` / `get_asset` — query generated assets
24+
- `list_storage` / `get_storage` / `add_storage` / `delete_storage` — manage S3 storage configs
25+
- `get_account` — retrieve account information
26+
- `get_status` — API health check
27+
- `list_templates` / `get_template` — browse public template gallery
28+
- **Utility methods:**
29+
- `wait_for_job` — poll for job completion with configurable interval and timeout
30+
- `download_job_result` — download binary output from completed jobs
31+
- `CloudLayerio::Util::HtmlUtil.encode_html` — Base64-encode HTML for the API
32+
- **HTTP transport:**
33+
- Retry logic with exponential backoff and jitter (429, 500-504)
34+
- Multipart file upload support
35+
- Redirect following for presigned S3 URLs
36+
- Configurable timeout, max retries, custom headers
37+
- **Error handling:**
38+
- `AuthError` (401/403), `RateLimitError` (429 with retry_after), `ApiError` (4xx/5xx)
39+
- `TimeoutError`, `NetworkError`, `ValidationError`, `ConfigError`
40+
- **Type system:**
41+
- Frozen constant modules (PdfFormat, ImageType, JobStatus, JobType, WaitUntilOption)
42+
- Option classes with keyword arguments and camelCase JSON serialization
43+
- Response classes with `from_hash` deserialization
44+
- `NOT_SET` sentinel for three-state `emulate_media_type`
45+
- Client-side validation for all endpoints
46+
- RSpec test suite with >97% coverage

README.md

Lines changed: 286 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,306 @@
11
# cloudlayerio
22

3-
Official Ruby SDK for the [CloudLayer.io](https://cloudlayer.io) document generation API.
3+
Official Ruby SDK for the [cloudlayer.io](https://cloudlayer.io) document generation API.
44

5-
## Status: In Development
5+
[![Gem Version](https://badge.fury.io/rb/cloudlayerio.svg)](https://rubygems.org/gems/cloudlayerio)
6+
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
67

7-
This SDK is currently under development. See the [CHANGELOG](CHANGELOG.md) for progress.
8+
Convert HTML, URLs, and templates to PDF and images. Supports file conversions (DOCX to PDF, PDF to DOCX), PDF merging, and a visual template editor.
9+
10+
## Requirements
11+
12+
- Ruby >= 3.1
13+
- One runtime dependency: `base64` gem (bundled with Ruby; extracted to a separate gem in Ruby 3.4+)
814

915
## Installation
1016

11-
Add this line to your application's Gemfile:
17+
Add to your Gemfile:
1218

1319
```ruby
14-
gem "cloudlayerio"
20+
gem "cloudlayerio", "~> 0.1"
1521
```
1622

17-
And then execute:
23+
Then run `bundle install`. Or install directly:
1824

1925
```bash
20-
bundle install
26+
gem install cloudlayerio
2127
```
2228

23-
Or install it yourself as:
29+
## Quick Start
30+
31+
```ruby
32+
require "cloudlayerio"
33+
34+
# v2 (async) — returns a Job object
35+
client = CloudLayerio::Client.new(api_key: "your-api-key", api_version: :v2)
36+
result = client.url_to_pdf(url: "https://example.com", async: true, storage: true)
37+
job = client.wait_for_job(result.job.id)
38+
data = client.download_job_result(job)
39+
File.binwrite("output.pdf", data)
40+
41+
# v1 (sync) — returns binary data directly
42+
client = CloudLayerio::Client.new(api_key: "your-api-key", api_version: :v1)
43+
result = client.url_to_pdf(url: "https://example.com")
44+
File.binwrite("output.pdf", result.bytes)
45+
```
46+
47+
## Configuration
48+
49+
```ruby
50+
client = CloudLayerio::Client.new do |config|
51+
config.api_key = "your-api-key" # Required
52+
config.api_version = :v2 # Required — :v1 or :v2
53+
config.base_url = "https://api.cloudlayer.io" # Default
54+
config.timeout = 30 # Seconds, default 30
55+
config.max_retries = 2 # 0-5, default 2
56+
config.user_agent = "my-app/1.0" # Custom user agent
57+
config.headers = { "X-Custom" => "value" } # Extra headers
58+
end
59+
```
60+
61+
## Conversion Methods
62+
63+
### URL to PDF
64+
65+
```ruby
66+
result = client.url_to_pdf(
67+
url: "https://example.com",
68+
format: "a4",
69+
print_background: true,
70+
margin: CloudLayerio::Options::Margin.new(top: "10px", bottom: "10px")
71+
)
72+
```
73+
74+
### URL to Image
75+
76+
```ruby
77+
result = client.url_to_image(
78+
url: "https://example.com",
79+
image_type: "png",
80+
quality: 90
81+
)
82+
```
83+
84+
### HTML to PDF
85+
86+
```ruby
87+
html = CloudLayerio::Util::HtmlUtil.encode_html("<h1>Hello World</h1><p>Generated with cloudlayer.io</p>")
88+
result = client.html_to_pdf(html: html, format: "letter")
89+
```
90+
91+
### HTML to Image
92+
93+
```ruby
94+
html = CloudLayerio::Util::HtmlUtil.encode_html("<div style='padding:20px'>Screenshot</div>")
95+
result = client.html_to_image(html: html, image_type: "png")
96+
```
97+
98+
### Template to PDF
99+
100+
```ruby
101+
result = client.template_to_pdf(
102+
template_id: "your-template-id",
103+
data: { name: "John Doe", invoice_number: "INV-001" }
104+
)
105+
```
106+
107+
### Template to Image
108+
109+
```ruby
110+
result = client.template_to_image(
111+
template_id: "your-template-id",
112+
data: { title: "Certificate of Completion" },
113+
image_type: "png"
114+
)
115+
```
116+
117+
### DOCX to PDF
118+
119+
```ruby
120+
result = client.docx_to_pdf(file: "/path/to/document.docx")
121+
```
122+
123+
### DOCX to HTML
124+
125+
```ruby
126+
result = client.docx_to_html(file: "/path/to/document.docx")
127+
```
128+
129+
### PDF to DOCX
130+
131+
```ruby
132+
result = client.pdf_to_docx(file: "/path/to/document.pdf")
133+
```
134+
135+
### Merge PDFs
136+
137+
```ruby
138+
result = client.merge_pdfs(
139+
batch: CloudLayerio::Options::Batch.new(urls: [
140+
"https://example.com/page1.pdf",
141+
"https://example.com/page2.pdf"
142+
])
143+
)
144+
```
145+
146+
## Working with v2 Results
147+
148+
v2 API returns a Job object. Poll for completion, then download:
149+
150+
```ruby
151+
client = CloudLayerio::Client.new(api_key: "your-key", api_version: :v2)
152+
153+
# Start conversion
154+
result = client.url_to_pdf(url: "https://example.com", async: true, storage: true)
155+
puts "Job created: #{result.job.id}"
156+
157+
# Wait for completion (polls every 5 seconds, up to 5 minutes)
158+
job = client.wait_for_job(result.job.id)
159+
puts "Job completed: #{job.status}"
160+
161+
# Download the result
162+
pdf_data = client.download_job_result(job)
163+
File.binwrite("output.pdf", pdf_data)
164+
```
165+
166+
## Data Management
167+
168+
### Jobs
169+
170+
```ruby
171+
jobs = client.list_jobs # Up to 10 most recent
172+
job = client.get_job("job-id")
173+
```
174+
175+
### Assets
176+
177+
```ruby
178+
assets = client.list_assets # Up to 10 most recent
179+
asset = client.get_asset("asset-id")
180+
```
181+
182+
### Storage
183+
184+
```ruby
185+
storages = client.list_storage
186+
detail = client.get_storage("storage-id")
187+
188+
# Create storage configuration
189+
resp = client.add_storage(
190+
title: "My S3",
191+
region: "us-east-1",
192+
access_key_id: "AKIA...",
193+
secret_access_key: "...",
194+
bucket: "my-bucket"
195+
)
196+
197+
# Delete storage configuration
198+
client.delete_storage("storage-id")
199+
```
200+
201+
### Account
202+
203+
```ruby
204+
account = client.get_account
205+
puts "Email: #{account.email}, Calls: #{account.calls}/#{account.calls_limit}"
206+
207+
status = client.get_status
208+
puts status.status # "ok "
209+
```
210+
211+
### Templates
212+
213+
```ruby
214+
templates = client.list_templates(type: "pdf", category: "invoice")
215+
template = client.get_template("template-id")
216+
```
217+
218+
## Error Handling
219+
220+
```ruby
221+
begin
222+
result = client.url_to_pdf(url: "https://example.com")
223+
rescue CloudLayerio::AuthError => e
224+
puts "Authentication failed: #{e.message} (#{e.status_code})"
225+
rescue CloudLayerio::RateLimitError => e
226+
puts "Rate limited, retry after #{e.retry_after} seconds"
227+
rescue CloudLayerio::ApiError => e
228+
puts "API error #{e.status_code}: #{e.message}"
229+
rescue CloudLayerio::TimeoutError => e
230+
puts "Request timed out: #{e.message}"
231+
rescue CloudLayerio::NetworkError => e
232+
puts "Connection failed: #{e.message}"
233+
rescue CloudLayerio::ValidationError => e
234+
puts "Invalid input: #{e.message}"
235+
rescue CloudLayerio::ConfigError => e
236+
puts "Bad configuration: #{e.message}"
237+
end
238+
```
239+
240+
**Error hierarchy:**
241+
242+
```
243+
CloudLayerio::Error < StandardError
244+
├── ConfigError (invalid client config)
245+
├── ValidationError (client-side input validation)
246+
├── NetworkError (connection/DNS failure)
247+
├── TimeoutError (request timeout)
248+
└── ApiError (HTTP 4xx/5xx)
249+
├── AuthError (401/403)
250+
└── RateLimitError (429, includes retry_after)
251+
```
252+
253+
## Advanced Options
254+
255+
### Viewport and Margins
256+
257+
```ruby
258+
result = client.url_to_pdf(
259+
url: "https://example.com",
260+
view_port: CloudLayerio::Options::Viewport.new(width: 1920, height: 1080, is_mobile: false),
261+
margin: CloudLayerio::Options::Margin.new(top: "1in", bottom: "1in", left: "0.5in", right: "0.5in")
262+
)
263+
```
264+
265+
### Cookies and Authentication
266+
267+
```ruby
268+
result = client.url_to_pdf(
269+
url: "https://example.com/dashboard",
270+
authentication: CloudLayerio::Options::Authentication.new(username: "user", password: "pass"),
271+
cookies: [CloudLayerio::Options::Cookie.new(name: "session", value: "abc123", http_only: true)]
272+
)
273+
```
274+
275+
### Async with Storage and Webhooks
276+
277+
```ruby
278+
result = client.url_to_pdf(
279+
url: "https://example.com",
280+
async: true,
281+
storage: true,
282+
webhook: "https://your-app.com/webhook"
283+
)
284+
```
285+
286+
### Batch Processing
287+
288+
```ruby
289+
result = client.url_to_pdf(
290+
batch: CloudLayerio::Options::Batch.new(urls: [
291+
"https://example.com/page1",
292+
"https://example.com/page2"
293+
])
294+
)
295+
```
296+
297+
## API Reference
298+
299+
Generate YARD documentation locally:
24300

25301
```bash
26-
gem install cloudlayerio
302+
bundle exec yard doc
303+
open doc/index.html
27304
```
28305

29306
## License

0 commit comments

Comments
 (0)