brave_search v0.1.0

A Crystal library for the Brave Search API with full type safety, comprehensive endpoint support, and excellent developer experience.

Brave Search Crystal Library

A Crystal library for the Brave Search API with full type safety, comprehensive endpoint support, and excellent developer experience.

Features

  • Complete API Coverage: Web, News, Image, Video, Suggest, Spellcheck, Local POI, Local Descriptions, Rich Results, and Summarizer endpoints
  • Type Safety: Leverages Crystal's static typing with enums for parameters like SafeSearch, Freshness, and Units
  • Robust HTTP Handling: Configurable timeouts, automatic retries, and gzip/deflate decompression
  • Comprehensive Error Handling: Typed exceptions for authentication, rate limiting, validation, and timeout errors
  • Testable: HTTP adapter abstraction enables testing without live API calls

Installation

Add the dependency to your shard.yml:

dependencies:
  brave_search:
    github: amscotti/brave_search

Then run:

shards install

Quick Start

require "brave_search"

client = BraveSearch::Client.new(ENV["BRAVE_API_KEY"])

# Simple web search
results = client.web_search("crystal programming language")

results.web_results.each do |result|
  puts "#{result.title}: #{result.url}"
end

Usage Examples

Web Search with Options

results = client.web_search("crystal programming",
  count: 10,
  safesearch: BraveSearch::Types::SafeSearch::Moderate,
  freshness: BraveSearch::Types::Freshness::PastWeek,
  units: BraveSearch::Types::Units::Metric,
  extra_snippets: true
)

# Custom date range
range = BraveSearch::Types::FreshnessRange.new(
  Time.utc(2024, 1, 1),
  Time.utc(2024, 6, 30)
)
results = client.web_search("crystal news", freshness: range)

News Search

results = client.news_search("technology news", count: 5)

results.results.each do |article|
  puts "#{article.title} (#{article.age})"
  puts "  Source: #{article.source.try(&.name)}"
  puts "  Breaking: #{article.breaking}"
end

Image Search

results = client.image_search("golden gate bridge", count: 20)

results.results.each do |image|
  puts "#{image.title}"
  puts "  Thumbnail: #{image.thumbnail.try(&.src)}"
  puts "  Size: #{image.properties.try(&.width)}x#{image.properties.try(&.height)}"
end

Video Search

results = client.video_search("crystal tutorial")

results.results.each do |video|
  puts "#{video.title}"
  puts "  Duration: #{video.video.try(&.duration)}"
  puts "  Views: #{video.video.try(&.views)}"
end

Search Suggestions

results = client.suggest("crystal lang", rich: true)

results.results.each do |suggestion|
  puts suggestion.query
end

Spellcheck

results = client.spellcheck("progrming")

results.results.each do |correction|
  puts "Did you mean: #{correction.query}"
end

Local Search (Two-Step Process)

# Step 1: Get location IDs from web search
web_results = client.web_search("coffee shops near me")

location_ids = web_results.locations.try(&.results.map(&.id)) || [] of String

# Step 2: Get detailed POI info
if location_ids.any?
  pois = client.local_pois(location_ids)

  pois.results.each do |poi|
    puts "#{poi.name}"
    puts "  Rating: #{poi.rating.try(&.rating_value)}/5"
    puts "  Address: #{poi.address.try(&.display_address)}"
    puts "  Price: #{poi.price_range}"
  end

  # Get AI-generated descriptions
  descriptions = client.local_descriptions(location_ids)
  descriptions.results.each do |desc|
    puts "#{desc.id}: #{desc.description}"
  end
end

Summarizer

# Enable summary in web search
web_results = client.web_search("what is crystal programming language", summary: true)

if summary_key = web_results.query.summary_key
  summary = client.summarizer(summary_key)

  puts "Title: #{summary.title}"
  summary.summary.try(&.each do |message|
    puts message.data if message.type == "text"
  end)

  puts "\nFollow-up questions:"
  summary.followups.try(&.each { |q| puts "  - #{q}" })
end

Configuration

config = BraveSearch::Config.new
config.timeout = 60.seconds        # HTTP request timeout (default: 30s)
config.retry_count = 5             # Number of retries on network errors (default: 3)
config.retry_delay = 2.seconds     # Delay between retries (default: 1s)
config.poll_interval = 1.second    # Summarizer polling interval (default: 500ms)
config.max_poll_attempts = 30      # Max summarizer poll attempts (default: 20)

client = BraveSearch::Client.new(ENV["BRAVE_API_KEY"], config: config)

Error Handling

begin
  results = client.web_search("test")
rescue BraveSearch::ValidationError => e
  puts "Invalid parameter '#{e.field}': #{e.message}"
rescue BraveSearch::AuthenticationError
  puts "Invalid API key"
rescue BraveSearch::RateLimitError => e
  if retry_after = e.retry_after
    puts "Rate limited. Retry after #{retry_after.total_seconds} seconds"
  end
rescue BraveSearch::TimeoutError
  puts "Request timed out"
rescue BraveSearch::HTTPError => e
  puts "HTTP error #{e.status}: #{e.body}"
rescue BraveSearch::APIError => e
  puts "API error: #{e.message}"
end

Type-Safe Enums

The library provides enums for type-safe parameter values:

# SafeSearch levels
BraveSearch::Types::SafeSearch::Off
BraveSearch::Types::SafeSearch::Moderate
BraveSearch::Types::SafeSearch::Strict

# Freshness options
BraveSearch::Types::Freshness::PastDay   # pd
BraveSearch::Types::Freshness::PastWeek  # pw
BraveSearch::Types::Freshness::PastMonth # pm
BraveSearch::Types::Freshness::PastYear  # py

# Units
BraveSearch::Types::Units::Metric
BraveSearch::Types::Units::Imperial

Testing

The library uses an HTTP adapter pattern for testability:

require "brave_search"
require "brave_search/http/mock_adapter"

adapter = BraveSearch::HTTP::MockAdapter.new
adapter.stub_json("/web/search", %({"type": "search", ...}))

client = BraveSearch::Client.new("test-key", adapter)
result = client.web_search("test")

# Verify requests
adapter.requests.first.headers["X-Subscription-Token"].should eq("test-key")

API Rate Limits

Rate limits vary by plan:

Plan Requests/Second Requests/Month
Free 1 2,000
Base 1 20,000
Pro 20 Unlimited

Development

# Install dependencies
shards install

# Run tests
crystal spec

# Run linter
./bin/ameba

# Format code
crystal tool format

Contributing

  1. Fork it (https://github.com/amscotti/brave_search/fork)
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request

License

MIT License - see LICENSE for details.

References

Repository

brave_search

Owner
Statistic
  • 0
  • 0
  • 0
  • 0
  • 1
  • about 7 hours ago
  • January 17, 2026
License

MIT License

Links
Synced at

Sun, 18 Jan 2026 00:43:34 GMT

Languages