loading
Generated 2025-10-08T23:59:28+00:00

All Files ( 100.0% covered at 1.48 hits/line )

31 files in total.
354 relevant lines, 354 lines covered and 0 lines missed. ( 100.0% )
23 total branches, 23 branches covered and 0 branches missed. ( 100.0% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line Branch Coverage Branches Covered branches Missed branches
lib/kagi/api.rb 100.00 % 24 13 13 0 1.77 100.00 % 0 0 0
lib/kagi/api/client.rb 100.00 % 33 13 13 0 2.38 100.00 % 2 2 0
lib/kagi/api/configuration/content.rb 100.00 % 16 7 7 0 1.00 100.00 % 0 0 0
lib/kagi/api/configuration/loader.rb 100.00 % 22 8 8 0 1.25 100.00 % 0 0 0
lib/kagi/api/container.rb 100.00 % 37 23 23 0 1.13 100.00 % 0 0 0
lib/kagi/api/contracts/error.rb 100.00 % 18 9 9 0 1.00 100.00 % 0 0 0
lib/kagi/api/contracts/fast.rb 100.00 % 18 9 9 0 1.00 100.00 % 0 0 0
lib/kagi/api/contracts/meta.rb 100.00 % 15 8 8 0 1.13 100.00 % 0 0 0
lib/kagi/api/contracts/reference.rb 100.00 % 14 7 7 0 1.00 100.00 % 0 0 0
lib/kagi/api/contracts/search.rb 100.00 % 27 16 16 0 1.00 100.00 % 0 0 0
lib/kagi/api/contracts/summary.rb 100.00 % 17 8 8 0 1.00 100.00 % 0 0 0
lib/kagi/api/dependencies.rb 100.00 % 9 4 4 0 1.00 100.00 % 0 0 0
lib/kagi/api/endpoints/container.rb 100.00 % 23 12 12 0 1.42 100.00 % 0 0 0
lib/kagi/api/endpoints/dependencies.rb 100.00 % 11 5 5 0 1.00 100.00 % 0 0 0
lib/kagi/api/endpoints/enrich/news.rb 100.00 % 57 24 24 0 1.17 100.00 % 3 3 0
lib/kagi/api/endpoints/enrich/web.rb 100.00 % 57 24 24 0 1.17 100.00 % 3 3 0
lib/kagi/api/endpoints/fast.rb 100.00 % 55 23 23 0 1.17 100.00 % 3 3 0
lib/kagi/api/endpoints/search.rb 100.00 % 55 23 23 0 1.17 100.00 % 3 3 0
lib/kagi/api/endpoints/summarize.rb 100.00 % 55 23 23 0 1.17 100.00 % 3 3 0
lib/kagi/api/models/content/error.rb 100.00 % 22 10 10 0 3.10 100.00 % 0 0 0
lib/kagi/api/models/content/fast.rb 100.00 % 16 7 7 0 1.71 100.00 % 0 0 0
lib/kagi/api/models/content/meta.rb 100.00 % 20 9 9 0 4.44 100.00 % 0 0 0
lib/kagi/api/models/content/reference.rb 100.00 % 12 5 5 0 1.00 100.00 % 0 0 0
lib/kagi/api/models/content/search.rb 100.00 % 35 11 11 0 3.09 100.00 % 2 2 0
lib/kagi/api/models/content/summary.rb 100.00 % 12 5 5 0 1.00 100.00 % 0 0 0
lib/kagi/api/models/content/thumbnail.rb 100.00 % 16 7 7 0 1.43 100.00 % 0 0 0
lib/kagi/api/models/error.rb 100.00 % 19 7 7 0 2.43 100.00 % 0 0 0
lib/kagi/api/models/fast.rb 100.00 % 19 6 6 0 1.17 100.00 % 0 0 0
lib/kagi/api/models/search.rb 100.00 % 19 7 7 0 2.00 100.00 % 0 0 0
lib/kagi/api/models/summary.rb 100.00 % 19 6 6 0 1.17 100.00 % 0 0 0
lib/kagi/api/requester.rb 100.00 % 31 15 15 0 1.67 100.00 % 4 4 0

lib/kagi/api.rb

100.0% lines covered

100.0% branches covered

13 relevant lines. 13 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 require "dry/schema"
  3. 1 require "zeitwerk"
  4. 1 Dry::Schema.load_extensions :monads
  5. 1 Zeitwerk::Loader.new.then do |loader|
  6. 1 loader.inflector.inflect "api" => "API"
  7. 1 loader.tag = "kagi-api"
  8. 1 loader.push_dir "#{__dir__}/.."
  9. 1 loader.setup
  10. end
  11. 1 module Kagi
  12. # Main namespace.
  13. 1 module API
  14. 1 def self.loader registry = Zeitwerk::Registry
  15. 11 @loader ||= registry.loaders.each.find { |loader| loader.tag == "kagi-api" }
  16. end
  17. 1 def self.new(&) = Client.new(&)
  18. end
  19. end

lib/kagi/api/client.rb

100.0% lines covered

100.0% branches covered

13 relevant lines. 13 lines covered and 0 lines missed.
2 total branches, 2 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Kagi
  3. 1 module API
  4. # Provides the primary client for making API requests.
  5. 1 class Client
  6. 1 include Dependencies[:settings]
  7. 1 include Endpoints::Dependencies[
  8. endpoint_enrich_news: "enrich.news",
  9. endpoint_enrich_web: "enrich.web",
  10. endpoint_fast: :fast,
  11. endpoint_search: :search,
  12. endpoint_summarize: :summarize
  13. ]
  14. 1 def initialize(**)
  15. 10 super
  16. 10 then: 2 else: 8 yield settings if block_given?
  17. end
  18. 1 def enrich_news(**) = endpoint_enrich_news.call(**)
  19. 1 def enrich_web(**) = endpoint_enrich_web.call(**)
  20. 1 def fast(**) = endpoint_fast.call(**)
  21. 1 def search(**) = endpoint_search.call(**)
  22. 1 def summarize(**) = endpoint_summarize.call(**)
  23. end
  24. end
  25. end

lib/kagi/api/configuration/content.rb

100.0% lines covered

100.0% branches covered

7 relevant lines. 7 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 require "inspectable"
  3. 1 module Kagi
  4. 1 module API
  5. 1 module Configuration
  6. # Defines customizable API configuration content.
  7. 1 Content = Struct.new :content_type, :token, :uri do
  8. 1 include Inspectable[token: :redact]
  9. 1 def headers = {"Content-Type" => content_type}.compact
  10. end
  11. end
  12. end
  13. end

lib/kagi/api/configuration/loader.rb

100.0% lines covered

100.0% branches covered

8 relevant lines. 8 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 require "initable"
  3. 1 module Kagi
  4. 1 module API
  5. 1 module Configuration
  6. # Loads configuration based on environment or falls back to defaults.
  7. 1 class Loader
  8. 1 include Initable[model: Content, environment: ENV]
  9. 1 def call
  10. 3 model[
  11. content_type: environment.fetch("KAGI_API_CONTENT_TYPE", "application/json"),
  12. token: environment["KAGI_API_TOKEN"],
  13. uri: environment.fetch("KAGI_API_URI", "https://kagi.com/api/v0")
  14. ]
  15. end
  16. end
  17. end
  18. end
  19. end

lib/kagi/api/container.rb

100.0% lines covered

100.0% branches covered

23 relevant lines. 23 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 require "cogger"
  3. 1 require "containable"
  4. 1 require "http"
  5. 1 module Kagi
  6. 1 module API
  7. # Registers application dependencies.
  8. 1 module Container
  9. 1 extend Containable
  10. 2 register(:settings) { Configuration::Loader.new.call }
  11. 2 register(:requester) { Requester.new }
  12. 2 register(:logger) { Cogger.new id: "kagi-api", formatter: :json }
  13. 1 register :http do
  14. 1 HTTP.default_options = HTTP::Options.new features: {logging: {logger: self[:logger]}}
  15. 1 HTTP
  16. end
  17. 1 namespace :contracts do
  18. 1 register :error, Contracts::Error
  19. 1 register :fast, Contracts::Fast
  20. 1 register :search, Contracts::Search
  21. 1 register :summary, Contracts::Summary
  22. end
  23. 1 namespace :models do
  24. 1 register :error, Models::Error
  25. 1 register :fast, Models::Fast
  26. 1 register :search, Models::Search
  27. 1 register :summary, Models::Summary
  28. end
  29. end
  30. end
  31. end

lib/kagi/api/contracts/error.rb

100.0% lines covered

100.0% branches covered

9 relevant lines. 9 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Kagi
  3. 1 module API
  4. 1 module Contracts
  5. # Validates error data.
  6. 1 Error = Dry::Schema.JSON do
  7. 1 required(:meta).hash Meta
  8. 1 required(:error).array(:hash) do
  9. 1 required(:code).filled :integer
  10. 1 required(:msg).filled :string
  11. 1 required(:ref).maybe :string
  12. end
  13. end
  14. end
  15. end
  16. end

lib/kagi/api/contracts/fast.rb

100.0% lines covered

100.0% branches covered

9 relevant lines. 9 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Kagi
  3. 1 module API
  4. 1 module Contracts
  5. # Validates Fast GPT data.
  6. 1 Fast = Dry::Schema.JSON do
  7. 1 required(:meta).hash Meta
  8. 1 required(:data).hash do
  9. 1 required(:output).filled :string
  10. 1 required(:tokens).filled :integer
  11. 1 required(:references).array(Reference)
  12. end
  13. end
  14. end
  15. end
  16. end

lib/kagi/api/contracts/meta.rb

100.0% lines covered

100.0% branches covered

8 relevant lines. 8 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Kagi
  3. 1 module API
  4. 1 module Contracts
  5. # Validates meta data.
  6. 1 Meta = Dry::Schema.JSON do
  7. 1 required(:id).filled :string
  8. 1 required(:node).filled :string
  9. 1 required(:ms).filled :integer
  10. 2 optional(:api_balance) { filled? > int? | float? }
  11. end
  12. end
  13. end
  14. end

lib/kagi/api/contracts/reference.rb

100.0% lines covered

100.0% branches covered

7 relevant lines. 7 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Kagi
  3. 1 module API
  4. 1 module Contracts
  5. # Validates reference data.
  6. 1 Reference = Dry::Schema.JSON do
  7. 1 required(:title).filled :string
  8. 1 required(:snippet).filled :string
  9. 1 required(:url).filled :string
  10. end
  11. end
  12. end
  13. end

lib/kagi/api/contracts/search.rb

100.0% lines covered

100.0% branches covered

16 relevant lines. 16 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Kagi
  3. 1 module API
  4. 1 module Contracts
  5. # Validates search data.
  6. 1 Search = Dry::Schema.JSON do
  7. 1 required(:meta).hash Meta
  8. 1 required(:data).array(:hash) do
  9. 1 required(:t).filled :integer
  10. 1 optional(:rank).filled :integer
  11. 1 optional(:url).filled :string
  12. 1 optional(:title).filled :string
  13. 1 optional(:snippet).maybe :string
  14. 1 optional(:published).filled :time
  15. 1 optional(:thumbnail).hash do
  16. 1 required(:url).filled :string
  17. 1 optional(:width).filled :integer
  18. 1 optional(:height).filled :integer
  19. end
  20. end
  21. end
  22. end
  23. end
  24. end

lib/kagi/api/contracts/summary.rb

100.0% lines covered

100.0% branches covered

8 relevant lines. 8 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Kagi
  3. 1 module API
  4. 1 module Contracts
  5. # Validates summary data.
  6. 1 Summary = Dry::Schema.JSON do
  7. 1 required(:meta).hash Meta
  8. 1 required(:data).hash do
  9. 1 required(:output).filled :string
  10. 1 required(:tokens).filled :integer
  11. end
  12. end
  13. end
  14. end
  15. end

lib/kagi/api/dependencies.rb

100.0% lines covered

100.0% branches covered

4 relevant lines. 4 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 require "infusible"
  3. 1 module Kagi
  4. 1 module API
  5. 1 Dependencies = Infusible[Container]
  6. end
  7. end

lib/kagi/api/endpoints/container.rb

100.0% lines covered

100.0% branches covered

12 relevant lines. 12 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 require "containable"
  3. 1 module Kagi
  4. 1 module API
  5. 1 module Endpoints
  6. # Registers all endpoints.
  7. 1 module Container
  8. 1 extend Containable
  9. 1 namespace :enrich do
  10. 2 register(:news) { Enrich::News.new }
  11. 2 register(:web) { Enrich::Web.new }
  12. end
  13. 2 register(:fast) { Fast.new }
  14. 2 register(:search) { Search.new }
  15. 2 register(:summarize) { Summarize.new }
  16. end
  17. end
  18. end
  19. end

lib/kagi/api/endpoints/dependencies.rb

100.0% lines covered

100.0% branches covered

5 relevant lines. 5 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 require "infusible"
  3. 1 module Kagi
  4. 1 module API
  5. 1 module Endpoints
  6. 1 Dependencies = Infusible[Container]
  7. end
  8. end
  9. end

lib/kagi/api/endpoints/enrich/news.rb

100.0% lines covered

100.0% branches covered

24 relevant lines. 24 lines covered and 0 lines missed.
3 total branches, 3 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 require "dry/monads"
  3. 1 require "inspectable"
  4. 1 require "pipeable"
  5. 1 module Kagi
  6. 1 module API
  7. 1 module Endpoints
  8. 1 module Enrich
  9. # Handles news requests.
  10. 1 class News
  11. 1 include Kagi::API::Dependencies[
  12. :requester,
  13. contract: "contracts.search",
  14. error_contract: "contracts.error",
  15. model: "models.search",
  16. error_model: "models.error"
  17. ]
  18. 1 include Dry::Monads[:result]
  19. 1 include Pipeable
  20. 1 include Inspectable[contract: :class, error_contract: :class]
  21. 1 def call(**params)
  22. 3 result = requester.get("enrich/news", **params)
  23. 3 in: 1 case result
  24. 1 in: 1 in Success then success result
  25. 1 else: 1 in Failure(response) then failure response
  26. 1 else Failure "Unable to parse HTTP response."
  27. end
  28. end
  29. 1 private
  30. 1 def success result
  31. 1 pipe result,
  32. try(:parse, catch: JSON::ParserError),
  33. validate(contract, as: :to_h),
  34. to(model, :for)
  35. end
  36. 1 def failure response
  37. 1 pipe(
  38. response,
  39. try(:parse, catch: JSON::ParserError),
  40. validate(error_contract, as: :to_h),
  41. to(error_model, :for),
  42. 1 bind { Failure it }
  43. )
  44. end
  45. end
  46. end
  47. end
  48. end
  49. end

lib/kagi/api/endpoints/enrich/web.rb

100.0% lines covered

100.0% branches covered

24 relevant lines. 24 lines covered and 0 lines missed.
3 total branches, 3 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 require "dry/monads"
  3. 1 require "inspectable"
  4. 1 require "pipeable"
  5. 1 module Kagi
  6. 1 module API
  7. 1 module Endpoints
  8. 1 module Enrich
  9. # Handles web requests.
  10. 1 class Web
  11. 1 include Kagi::API::Dependencies[
  12. :requester,
  13. contract: "contracts.search",
  14. error_contract: "contracts.error",
  15. model: "models.search",
  16. error_model: "models.error"
  17. ]
  18. 1 include Dry::Monads[:result]
  19. 1 include Pipeable
  20. 1 include Inspectable[contract: :class, error_contract: :class]
  21. 1 def call(**params)
  22. 3 result = requester.get("enrich/web", **params)
  23. 3 in: 1 case result
  24. 1 in: 1 in Success then success result
  25. 1 else: 1 in Failure(response) then failure response
  26. 1 else Failure "Unable to parse HTTP response."
  27. end
  28. end
  29. 1 private
  30. 1 def success result
  31. 1 pipe result,
  32. try(:parse, catch: JSON::ParserError),
  33. validate(contract, as: :to_h),
  34. to(model, :for)
  35. end
  36. 1 def failure response
  37. 1 pipe(
  38. response,
  39. try(:parse, catch: JSON::ParserError),
  40. validate(error_contract, as: :to_h),
  41. to(error_model, :for),
  42. 1 bind { Failure it }
  43. )
  44. end
  45. end
  46. end
  47. end
  48. end
  49. end

lib/kagi/api/endpoints/fast.rb

100.0% lines covered

100.0% branches covered

23 relevant lines. 23 lines covered and 0 lines missed.
3 total branches, 3 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 require "dry/monads"
  3. 1 require "inspectable"
  4. 1 require "pipeable"
  5. 1 module Kagi
  6. 1 module API
  7. 1 module Endpoints
  8. # Handles Fast GPT requests.
  9. 1 class Fast
  10. 1 include Kagi::API::Dependencies[
  11. :requester,
  12. contract: "contracts.fast",
  13. error_contract: "contracts.error",
  14. model: "models.fast",
  15. error_model: "models.error"
  16. ]
  17. 1 include Dry::Monads[:result]
  18. 1 include Pipeable
  19. 1 include Inspectable[contract: :class, error_contract: :class]
  20. 1 def call(**params)
  21. 3 result = requester.post("fastgpt", **params)
  22. 3 in: 1 case result
  23. 1 in: 1 in Success then success result
  24. 1 else: 1 in Failure(response) then failure response
  25. 1 else Failure "Unable to parse HTTP response."
  26. end
  27. end
  28. 1 private
  29. 1 def success result
  30. 1 pipe result,
  31. try(:parse, catch: JSON::ParserError),
  32. validate(contract, as: :to_h),
  33. to(model, :for)
  34. end
  35. 1 def failure response
  36. 1 pipe(
  37. response,
  38. try(:parse, catch: JSON::ParserError),
  39. validate(error_contract, as: :to_h),
  40. to(error_model, :for),
  41. 1 bind { Failure it }
  42. )
  43. end
  44. end
  45. end
  46. end
  47. end

lib/kagi/api/endpoints/search.rb

100.0% lines covered

100.0% branches covered

23 relevant lines. 23 lines covered and 0 lines missed.
3 total branches, 3 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 require "dry/monads"
  3. 1 require "inspectable"
  4. 1 require "pipeable"
  5. 1 module Kagi
  6. 1 module API
  7. 1 module Endpoints
  8. # Handles search requests.
  9. 1 class Search
  10. 1 include Kagi::API::Dependencies[
  11. :requester,
  12. contract: "contracts.search",
  13. error_contract: "contracts.error",
  14. model: "models.search",
  15. error_model: "models.error"
  16. ]
  17. 1 include Dry::Monads[:result]
  18. 1 include Pipeable
  19. 1 include Inspectable[contract: :class, error_contract: :class]
  20. 1 def call(**params)
  21. 3 result = requester.get("search", **params)
  22. 3 in: 1 case result
  23. 1 in: 1 in Success then success result
  24. 1 else: 1 in Failure(response) then failure response
  25. 1 else Failure "Unable to parse HTTP response."
  26. end
  27. end
  28. 1 private
  29. 1 def success result
  30. 1 pipe result,
  31. try(:parse, catch: JSON::ParserError),
  32. validate(contract, as: :to_h),
  33. to(model, :for)
  34. end
  35. 1 def failure response
  36. 1 pipe(
  37. response,
  38. try(:parse, catch: JSON::ParserError),
  39. validate(error_contract, as: :to_h),
  40. to(error_model, :for),
  41. 1 bind { Failure it }
  42. )
  43. end
  44. end
  45. end
  46. end
  47. end

lib/kagi/api/endpoints/summarize.rb

100.0% lines covered

100.0% branches covered

23 relevant lines. 23 lines covered and 0 lines missed.
3 total branches, 3 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 require "dry/monads"
  3. 1 require "inspectable"
  4. 1 require "pipeable"
  5. 1 module Kagi
  6. 1 module API
  7. 1 module Endpoints
  8. # Handles summarize requests.
  9. 1 class Summarize
  10. 1 include Kagi::API::Dependencies[
  11. :requester,
  12. contract: "contracts.summary",
  13. error_contract: "contracts.error",
  14. model: "models.summary",
  15. error_model: "models.error"
  16. ]
  17. 1 include Dry::Monads[:result]
  18. 1 include Pipeable
  19. 1 include Inspectable[contract: :class, error_contract: :class]
  20. 1 def call(**params)
  21. 3 result = requester.post("summarize", **params)
  22. 3 in: 1 case result
  23. 1 in: 1 in Success then success result
  24. 1 else: 1 in Failure(response) then failure response
  25. 1 else Failure "Unable to parse HTTP response."
  26. end
  27. end
  28. 1 private
  29. 1 def success result
  30. 1 pipe result,
  31. try(:parse, catch: JSON::ParserError),
  32. validate(contract, as: :to_h),
  33. to(model, :for)
  34. end
  35. 1 def failure response
  36. 1 pipe(
  37. response,
  38. try(:parse, catch: JSON::ParserError),
  39. validate(error_contract, as: :to_h),
  40. to(error_model, :for),
  41. 1 bind { Failure it }
  42. )
  43. end
  44. end
  45. end
  46. end
  47. end

lib/kagi/api/models/content/error.rb

100.0% lines covered

100.0% branches covered

10 relevant lines. 10 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Kagi
  3. 1 module API
  4. 1 module Models
  5. 1 module Content
  6. 1 ERROR_MAP = {msg: :message, ref: :reference}.freeze
  7. # Models error data.
  8. 1 Error = Data.define :code, :message, :reference do
  9. 1 def self.for(key_map: ERROR_MAP, **attributes)
  10. 7 new(**attributes.transform_keys(key_map))
  11. end
  12. 1 def initialize(reference: nil, **)
  13. 16 super
  14. end
  15. end
  16. end
  17. end
  18. end
  19. end

lib/kagi/api/models/content/fast.rb

100.0% lines covered

100.0% branches covered

7 relevant lines. 7 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Kagi
  3. 1 module API
  4. 1 module Models
  5. 1 module Content
  6. # Models fast data.
  7. 1 Fast = Data.define :output, :tokens, :references do
  8. 1 def self.for(**attributes)
  9. 6 new(**attributes, references: attributes[:references].map { Reference[**it] })
  10. end
  11. end
  12. end
  13. end
  14. end
  15. end

lib/kagi/api/models/content/meta.rb

100.0% lines covered

100.0% branches covered

9 relevant lines. 9 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Kagi
  3. 1 module API
  4. 1 module Models
  5. 1 module Content
  6. 1 META_MAP = {ms: :duration, api_balance: :balance}.freeze
  7. # Models meta data.
  8. 1 Meta = Data.define :id, :node, :duration, :balance do
  9. 1 def self.for(key_map: META_MAP, **attributes) = new(**attributes.transform_keys(key_map))
  10. 1 def initialize(balance: nil, **)
  11. 32 super
  12. end
  13. end
  14. end
  15. end
  16. end
  17. end

lib/kagi/api/models/content/reference.rb

100.0% lines covered

100.0% branches covered

5 relevant lines. 5 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Kagi
  3. 1 module API
  4. 1 module Models
  5. 1 module Content
  6. # Models reference data.
  7. 1 Reference = Data.define :title, :snippet, :url
  8. end
  9. end
  10. end
  11. end

lib/kagi/api/models/content/search.rb

100.0% lines covered

100.0% branches covered

11 relevant lines. 11 lines covered and 0 lines missed.
2 total branches, 2 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Kagi
  3. 1 module API
  4. 1 module Models
  5. 1 module Content
  6. 1 SEARCH_MAP = {t: :type, published: :published_at}.freeze
  7. # Models search data.
  8. 1 Search = Data.define :type, :rank, :title, :url, :snippet, :published_at, :thumbnail do
  9. 1 def self.for(key_map: SEARCH_MAP, **attributes)
  10. 6 new(
  11. **attributes.transform_keys(key_map),
  12. 6 then: 1 else: 5 thumbnail: (Thumbnail[**attributes[:thumbnail]] if attributes.key? :thumbnail)
  13. )
  14. end
  15. # rubocop:todo Metrics/ParameterLists
  16. 1 def initialize(
  17. rank: nil,
  18. title: nil,
  19. url: nil,
  20. snippet: nil,
  21. published_at: nil,
  22. thumbnail: nil,
  23. **attributes
  24. )
  25. 14 super
  26. end
  27. # rubocop:enable Metrics/ParameterLists
  28. end
  29. end
  30. end
  31. end
  32. end

lib/kagi/api/models/content/summary.rb

100.0% lines covered

100.0% branches covered

5 relevant lines. 5 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Kagi
  3. 1 module API
  4. 1 module Models
  5. 1 module Content
  6. # Models summary data.
  7. 1 Summary = Data.define :output, :tokens
  8. end
  9. end
  10. end
  11. end

lib/kagi/api/models/content/thumbnail.rb

100.0% lines covered

100.0% branches covered

7 relevant lines. 7 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Kagi
  3. 1 module API
  4. 1 module Models
  5. 1 module Content
  6. # Models thumbnail data.
  7. 1 Thumbnail = Data.define :url, :width, :height do
  8. 1 def initialize url:, width: nil, height: nil
  9. 4 super
  10. end
  11. end
  12. end
  13. end
  14. end
  15. end

lib/kagi/api/models/error.rb

100.0% lines covered

100.0% branches covered

7 relevant lines. 7 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Kagi
  3. 1 module API
  4. 1 module Models
  5. # Models the API error.
  6. 1 Error = Data.define :meta, :error do
  7. 1 def self.for(**attributes)
  8. 6 new(
  9. **attributes.merge!(
  10. meta: Content::Meta.for(**attributes[:meta]),
  11. 6 error: attributes[:error].map { Content::Error.for(**it) }
  12. )
  13. )
  14. end
  15. end
  16. end
  17. end
  18. end

lib/kagi/api/models/fast.rb

100.0% lines covered

100.0% branches covered

6 relevant lines. 6 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Kagi
  3. 1 module API
  4. 1 module Models
  5. # Models the fast payload.
  6. 1 Fast = Data.define :meta, :data do
  7. 1 def self.for(**attributes)
  8. 2 new(
  9. **attributes.merge!(
  10. meta: Content::Meta.for(**attributes[:meta]),
  11. data: Content::Fast.for(**attributes[:data])
  12. )
  13. )
  14. end
  15. end
  16. end
  17. end
  18. end

lib/kagi/api/models/search.rb

100.0% lines covered

100.0% branches covered

7 relevant lines. 7 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Kagi
  3. 1 module API
  4. 1 module Models
  5. # Models the search payload.
  6. 1 Search = Data.define :meta, :data do
  7. 1 def self.for(**attributes)
  8. 4 new(
  9. **attributes.merge!(
  10. meta: Content::Meta.for(**attributes[:meta]),
  11. 5 data: attributes[:data].map { Content::Search.for(**it) }
  12. )
  13. )
  14. end
  15. end
  16. end
  17. end
  18. end

lib/kagi/api/models/summary.rb

100.0% lines covered

100.0% branches covered

6 relevant lines. 6 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Kagi
  3. 1 module API
  4. 1 module Models
  5. # Models the API payload.
  6. 1 Summary = Data.define :meta, :data do
  7. 1 def self.for(**attributes)
  8. 2 new(
  9. **attributes.merge!(
  10. meta: Content::Meta.for(**attributes[:meta]),
  11. data: Content::Summary[**attributes[:data]]
  12. )
  13. )
  14. end
  15. end
  16. end
  17. end
  18. end

lib/kagi/api/requester.rb

100.0% lines covered

100.0% branches covered

15 relevant lines. 15 lines covered and 0 lines missed.
4 total branches, 4 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Kagi
  3. 1 module API
  4. # The low-level object for making basic HTTP requests.
  5. 1 class Requester
  6. 1 include Dependencies[:settings, :http]
  7. 1 include Dry::Monads[:result]
  8. 1 def initialize(**)
  9. 4 super
  10. 4 then: 1 else: 3 yield settings if block_given?
  11. end
  12. 1 def get(path, **params) = call(__method__, path, params:)
  13. 1 def post(path, **json) = call(__method__, path, json:)
  14. 1 private
  15. 1 attr_reader :settings
  16. 1 def call verb, path, **options
  17. 3 http.auth("Bot #{settings.token}")
  18. .headers(settings.headers)
  19. .public_send(verb, "#{settings.uri}/#{path}", options)
  20. 3 then: 2 else: 1 .then { |response| response.status.success? ? Success(response) : Failure(response) }
  21. end
  22. end
  23. end
  24. end