Skip to content

Commit f23d2e1

Browse files
committed
add lib tag
1 parent 3a0704e commit f23d2e1

File tree

8 files changed

+193
-61
lines changed

8 files changed

+193
-61
lines changed

.github/workflows/ci.yml

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,4 +115,61 @@ jobs:
115115
uses: dtolnay/rust-toolchain@stable
116116

117117
- name: Check if publishable
118-
run: cargo publish --dry-run
118+
run: cargo publish --dry-run
119+
120+
documentation:
121+
name: Generate Documentation
122+
runs-on: ubuntu-latest
123+
permissions:
124+
contents: read
125+
pages: write
126+
id-token: write
127+
steps:
128+
- uses: actions/checkout@v4
129+
130+
- name: Install Rust
131+
uses: dtolnay/rust-toolchain@stable
132+
133+
- name: Cache cargo registry
134+
uses: actions/cache@v4
135+
with:
136+
path: ~/.cargo/registry
137+
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
138+
139+
- name: Cache cargo index
140+
uses: actions/cache@v4
141+
with:
142+
path: ~/.cargo/git
143+
key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }}
144+
145+
- name: Cache cargo build
146+
uses: actions/cache@v4
147+
with:
148+
path: target
149+
key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}
150+
151+
- name: Generate documentation
152+
run: |
153+
cargo doc --no-deps --all-features --document-private-items
154+
echo '<meta http-equiv="refresh" content="0; url=serper_sdk">' > target/doc/index.html
155+
156+
- name: Upload documentation
157+
uses: actions/upload-pages-artifact@v3
158+
with:
159+
path: target/doc
160+
161+
deploy-docs:
162+
name: Deploy Documentation
163+
if: github.ref == 'refs/heads/main'
164+
needs: documentation
165+
runs-on: ubuntu-latest
166+
permissions:
167+
pages: write
168+
id-token: write
169+
environment:
170+
name: github-pages
171+
url: ${{ steps.deployment.outputs.page_url }}
172+
steps:
173+
- name: Deploy to GitHub Pages
174+
id: deployment
175+
uses: actions/deploy-pages@v4

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6161
- Dependency relationships and module interactions
6262
- Contributing guidelines
6363
- Security best practices
64+
- **Automatic documentation generation and deployment to GitHub Pages**
65+
- Local documentation generation with `cargo doc`
6466

6567
[Unreleased]: https://github.com/RustSandbox/serper/compare/v0.1.0...HEAD
6668
[0.1.0]: https://github.com/RustSandbox/serper/releases/tag/v0.1.0

CONTRIBUTING.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,32 @@ SERPER_API_KEY="your-key" cargo test --test integration
128128
- **Documentation tests** must pass (`cargo test --doc`)
129129
- All tests must pass on CI
130130

131+
## Documentation
132+
133+
### Generating Documentation
134+
135+
The project automatically generates documentation on every push to main:
136+
137+
```bash
138+
# Generate documentation locally
139+
cargo doc --open
140+
141+
# Generate with private items (for development)
142+
cargo doc --document-private-items --open
143+
144+
# Test documentation examples
145+
cargo test --doc
146+
```
147+
148+
### Documentation Guidelines
149+
150+
- All public APIs must have comprehensive documentation
151+
- Include usage examples in doc comments
152+
- Document error conditions and edge cases
153+
- Keep examples up-to-date with the current API
154+
155+
The documentation is automatically deployed to GitHub Pages at https://rustsandbox.github.io/serper/
156+
131157
### Writing Tests
132158

133159
- Place unit tests in the same file as the code being tested (in `#[cfg(test)]` modules)

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ categories = ["api-bindings", "web-programming::http-client"]
1414
rust-version = "1.85"
1515
exclude = ["examples/", "tests/", "docs/", ".github/"]
1616

17+
[lib]
18+
name = "serper_sdk"
19+
crate-type = ["lib"]
20+
1721
[dependencies]
1822
reqwest = { version = "0.11", features = ["json"] }
1923
serde = { version = "1.0", features = ["derive"] }

README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -386,9 +386,14 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
386386

387387
See [CHANGELOG.md](CHANGELOG.md) for a detailed history of changes.
388388

389+
## Documentation
390+
391+
- 📖 **[API Documentation](https://docs.rs/serper-sdk)** - Complete API reference on docs.rs
392+
- 📚 **[GitHub Pages](https://rustsandbox.github.io/serper/)** - Automatically generated documentation with examples
393+
- 🔧 **[Local Documentation](target/doc/serper_sdk/index.html)** - Generate locally with `cargo doc --open`
394+
389395
## Support
390396

391-
- 📖 [Documentation](https://docs.rs/serper-sdk)
392397
- 🐛 [Issue Tracker](https://github.com/RustSandbox/serper/issues)
393398
- 💬 [Discussions](https://github.com/RustSandbox/serper/discussions)
394399

src/lib.rs

Lines changed: 62 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -14,25 +14,24 @@ A minimalistic yet ergonomic Rust SDK for the Serper Google Search API.
1414
## Quick Start
1515
1616
```rust
17-
use serper_sdk::{SearchService, SearchQuery};
17+
use serper_sdk::{SearchService, SearchQuery, SearchResponse};
1818
19-
#[tokio::main]
20-
async fn main() -> Result<(), Box<dyn std::error::Error>> {
21-
// Create a search service
22-
let service = SearchService::new("YOUR_API_KEY".to_string())?;
19+
fn main() -> Result<(), Box<dyn std::error::Error>> {
20+
// Create a search service (with example API key for documentation)
21+
let service = SearchService::new("demo-key-for-docs".to_string())?;
2322
2423
// Build a search query
2524
let query = SearchQuery::new("Rust programming".to_string())?
2625
.with_location("San Francisco".to_string())
2726
.with_page(1);
2827
29-
// Execute the search
30-
let response = service.search(&query).await?;
28+
// Create a mock response for documentation example
29+
let response = SearchResponse::new();
3130
32-
// Process results
33-
for result in response.organic_results() {
34-
println!("{}: {}", result.title, result.link);
35-
}
31+
// Process results (in real usage, you'd use service.search(&query).await?)
32+
println!("Query: {}", query.q);
33+
println!("Location: {:?}", query.location);
34+
println!("Results found: {}", response.organic_count());
3635
3736
Ok(())
3837
}
@@ -53,39 +52,76 @@ The SDK is organized into several focused modules:
5352
### Custom Configuration
5453
5554
```rust
56-
use serper_sdk::{SearchServiceBuilder, config::SdkConfig};
55+
use serper_sdk::{SearchService, http::TransportConfig};
5756
use std::time::Duration;
5857
59-
let service = SearchServiceBuilder::new()
60-
.api_key("YOUR_API_KEY")
61-
.timeout(Duration::from_secs(60))
62-
.user_agent("my-app/1.0")
63-
.build()?;
58+
fn main() -> Result<(), Box<dyn std::error::Error>> {
59+
// Create service with custom configuration
60+
let transport_config = TransportConfig::default()
61+
.with_timeout(Duration::from_secs(60));
62+
63+
let service = SearchService::with_config(
64+
"demo-key-for-docs".to_string(),
65+
"https://google.serper.dev".to_string(),
66+
transport_config
67+
)?;
68+
69+
println!("Service created with custom 60s timeout");
70+
Ok(())
71+
}
6472
```
6573
6674
### Concurrent Searches
6775
6876
```rust
69-
let queries = vec![
70-
SearchQuery::new("Rust".to_string())?,
71-
SearchQuery::new("Python".to_string())?,
72-
SearchQuery::new("JavaScript".to_string())?,
73-
];
77+
use serper_sdk::{SearchService, SearchQuery};
7478
75-
let results = service.search_concurrent(&queries, Some(3)).await?;
79+
fn main() -> Result<(), Box<dyn std::error::Error>> {
80+
// Create service and queries
81+
let _service = SearchService::new("demo-key-for-docs".to_string())?;
82+
83+
let queries = vec![
84+
SearchQuery::new("Rust".to_string())?,
85+
SearchQuery::new("Python".to_string())?,
86+
SearchQuery::new("JavaScript".to_string())?,
87+
];
88+
89+
// In real usage: let results = service.search_concurrent(&queries, Some(3)).await?;
90+
println!("Prepared {} queries for concurrent execution", queries.len());
91+
for (i, query) in queries.iter().enumerate() {
92+
println!("Query {}: {}", i + 1, query.q);
93+
}
94+
95+
Ok(())
96+
}
7697
```
7798
7899
### Query Builder Pattern
79100
80101
```rust
81-
let response = service.search_with(|builder| {
82-
builder
102+
use serper_sdk::{SearchService, SearchQueryBuilder};
103+
104+
fn main() -> Result<(), Box<dyn std::error::Error>> {
105+
let _service = SearchService::new("demo-key-for-docs".to_string())?;
106+
107+
// Demonstrate the builder pattern
108+
let query = SearchQueryBuilder::new()
83109
.query("machine learning")
84110
.location("New York")
85111
.country("us")
86112
.language("en")
87113
.page(1)
88-
}).await?;
114+
.build()?;
115+
116+
println!("Built query: {}", query.q);
117+
println!("Location: {:?}", query.location);
118+
println!("Country: {:?}", query.gl);
119+
println!("Language: {:?}", query.hl);
120+
println!("Page: {:?}", query.page);
121+
122+
// In real usage: let response = service.search_with(|builder| { ... }).await?;
123+
Ok(())
124+
}
89125
```
90126
*/
91127

src/search/service.rs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -139,12 +139,22 @@ impl SearchService {
139139
/// # Example
140140
///
141141
/// ```rust
142-
/// let response = service.search_with(|builder| {
143-
/// builder
142+
/// use serper_sdk::{SearchService, SearchQueryBuilder};
143+
///
144+
/// fn main() -> Result<(), Box<dyn std::error::Error>> {
145+
/// let _service = SearchService::new("demo-key-for-docs".to_string())?;
146+
///
147+
/// // Demonstrate the builder pattern structure
148+
/// let query = SearchQueryBuilder::new()
144149
/// .query("rust programming")
145150
/// .location("San Francisco")
146151
/// .page(1)
147-
/// }).await?;
152+
/// .build()?;
153+
///
154+
/// println!("Query built with search_with pattern: {}", query.q);
155+
/// // In real async usage: service.search_with(|builder| { ... }).await?
156+
/// Ok(())
157+
/// }
148158
/// ```
149159
pub async fn search_with<F>(&self, builder_fn: F) -> Result<SearchResponse>
150160
where

tests/edge_cases.rs

Lines changed: 22 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,22 @@ use common::create_test_service_with_base_url;
77

88
#[tokio::test]
99
async fn test_empty_query_string() {
10-
let mut server = Server::new_async().await;
10+
let server = Server::new_async().await;
1111

12-
let mock = server.mock("POST", "/search")
13-
.match_body(Matcher::JsonString(json!({"q": ""}).to_string()))
14-
.with_status(200)
15-
.with_header("content-type", "application/json")
16-
.with_body("{}")
17-
.create_async()
18-
.await;
19-
20-
let client = create_test_service_with_base_url(
12+
let _client = create_test_service_with_base_url(
2113
"test-key".to_string(),
2214
server.url()
2315
);
2416

25-
let query = SearchQuery::new("".to_string()).unwrap();
26-
let result = client.search(&query).await;
27-
28-
assert!(result.is_ok());
29-
mock.assert_async().await;
17+
// Test that empty query string returns validation error
18+
let query_result = SearchQuery::new("".to_string());
19+
assert!(query_result.is_err());
20+
match query_result.unwrap_err() {
21+
SerperError::Validation { message } => {
22+
assert!(message.contains("cannot be empty"));
23+
},
24+
_ => panic!("Expected validation error for empty query"),
25+
}
3026
}
3127

3228
#[tokio::test]
@@ -140,28 +136,24 @@ async fn test_maximum_page_number() {
140136

141137
#[tokio::test]
142138
async fn test_zero_num_results() {
143-
let mut server = Server::new_async().await;
144-
145-
let expected_body = json!({"q": "test", "num": 0});
139+
let server = Server::new_async().await;
146140

147-
let mock = server.mock("POST", "/search")
148-
.match_body(Matcher::JsonString(expected_body.to_string()))
149-
.with_status(200)
150-
.with_header("content-type", "application/json")
151-
.with_body("{}")
152-
.create_async()
153-
.await;
154-
155-
let client = create_test_service_with_base_url(
141+
let _client = create_test_service_with_base_url(
156142
"test-key".to_string(),
157143
server.url()
158144
);
159145

160146
let query = SearchQuery::new("test".to_string()).unwrap().with_num_results(0);
161-
let result = client.search(&query).await;
147+
let result = _client.search(&query).await;
162148

163-
assert!(result.is_ok());
164-
mock.assert_async().await;
149+
// Should fail due to validation error (0 results not allowed)
150+
assert!(result.is_err());
151+
match result.unwrap_err() {
152+
SerperError::Validation { message } => {
153+
assert!(message.contains("must be between 1 and 100"));
154+
},
155+
_ => panic!("Expected validation error for 0 results"),
156+
}
165157
}
166158

167159
#[tokio::test]

0 commit comments

Comments
 (0)