ecfr 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. checksums.yaml +7 -0
  2. data/.circleci/config.yml +11 -0
  3. data/.gitignore +11 -0
  4. data/.rspec +3 -0
  5. data/.rspec_parallel +4 -0
  6. data/.rubocop.yml +28 -0
  7. data/.yardopts +3 -0
  8. data/CHANGELOG.md +6 -0
  9. data/Dockerfile +6 -0
  10. data/Gemfile +6 -0
  11. data/Gemfile.lock +138 -0
  12. data/LICENSE.txt +21 -0
  13. data/README.md +133 -0
  14. data/Rakefile +12 -0
  15. data/bin/console +15 -0
  16. data/bin/setup +8 -0
  17. data/ecfr.gemspec +74 -0
  18. data/lib/ecfr/admin_service/agency/hierarchy.rb +27 -0
  19. data/lib/ecfr/admin_service/agency.rb +34 -0
  20. data/lib/ecfr/admin_service/api_documentation.rb +7 -0
  21. data/lib/ecfr/admin_service/base.rb +26 -0
  22. data/lib/ecfr/admin_service/build.rb +38 -0
  23. data/lib/ecfr/admin_service/ecfr_correction/cfr_reference.rb +17 -0
  24. data/lib/ecfr/admin_service/ecfr_correction.rb +78 -0
  25. data/lib/ecfr/admin_service/editorial_note/hierarchy.rb +19 -0
  26. data/lib/ecfr/admin_service/editorial_note.rb +40 -0
  27. data/lib/ecfr/admin_service/ibr_cfr_range/address.rb +17 -0
  28. data/lib/ecfr/admin_service/ibr_cfr_range/organization.rb +28 -0
  29. data/lib/ecfr/admin_service/ibr_cfr_range.rb +67 -0
  30. data/lib/ecfr/admin_service/issue/change.rb +19 -0
  31. data/lib/ecfr/admin_service/issue.rb +86 -0
  32. data/lib/ecfr/admin_service/site_notification.rb +34 -0
  33. data/lib/ecfr/admin_service/status.rb +7 -0
  34. data/lib/ecfr/attribute_caster.rb +72 -0
  35. data/lib/ecfr/attribute_method_definition.rb +92 -0
  36. data/lib/ecfr/base.rb +71 -0
  37. data/lib/ecfr/client.rb +318 -0
  38. data/lib/ecfr/common/hierarchy.rb +35 -0
  39. data/lib/ecfr/configuration.rb +58 -0
  40. data/lib/ecfr/constants.rb +21 -0
  41. data/lib/ecfr/default_documentation_setup.rb +39 -0
  42. data/lib/ecfr/default_status_setup.rb +46 -0
  43. data/lib/ecfr/diff_service/base.rb +17 -0
  44. data/lib/ecfr/diff_service/status.rb +34 -0
  45. data/lib/ecfr/extensible.rb +45 -0
  46. data/lib/ecfr/facet_attribute_method_definition.rb +47 -0
  47. data/lib/ecfr/faraday/user_agent/middleware.rb +14 -0
  48. data/lib/ecfr/ofr_profile_service/base.rb +20 -0
  49. data/lib/ecfr/ofr_profile_service/status.rb +7 -0
  50. data/lib/ecfr/parallel_client.rb +33 -0
  51. data/lib/ecfr/prince_xml_service/base.rb +17 -0
  52. data/lib/ecfr/prince_xml_service/pdf.rb +31 -0
  53. data/lib/ecfr/renderer_service/base.rb +31 -0
  54. data/lib/ecfr/renderer_service/content.rb +34 -0
  55. data/lib/ecfr/renderer_service/diff.rb +31 -0
  56. data/lib/ecfr/renderer_service/origin.rb +56 -0
  57. data/lib/ecfr/renderer_service/status.rb +7 -0
  58. data/lib/ecfr/request_representation.rb +12 -0
  59. data/lib/ecfr/search_service/api_documentation.rb +7 -0
  60. data/lib/ecfr/search_service/base.rb +23 -0
  61. data/lib/ecfr/search_service/content_version/count.rb +33 -0
  62. data/lib/ecfr/search_service/content_version/hierarchical_count.rb +17 -0
  63. data/lib/ecfr/search_service/content_version/hierarchical_count_node.rb +30 -0
  64. data/lib/ecfr/search_service/content_version/hierarchichal_result.rb +42 -0
  65. data/lib/ecfr/search_service/content_version/result.rb +110 -0
  66. data/lib/ecfr/search_service/content_version/suggestion.rb +76 -0
  67. data/lib/ecfr/search_service/content_version/summary.rb +27 -0
  68. data/lib/ecfr/search_service/content_version.rb +85 -0
  69. data/lib/ecfr/search_service/date_facet.rb +19 -0
  70. data/lib/ecfr/search_service/facet_base.rb +55 -0
  71. data/lib/ecfr/search_service/status.rb +7 -0
  72. data/lib/ecfr/search_service/title_facet.rb +18 -0
  73. data/lib/ecfr/subscriptions_service/base.rb +19 -0
  74. data/lib/ecfr/subscriptions_service/status.rb +7 -0
  75. data/lib/ecfr/subscriptions_service/subscription.rb +97 -0
  76. data/lib/ecfr/testing/extensions/admin_service/ecfr_correction_extensions.rb +13 -0
  77. data/lib/ecfr/testing/extensions/admin_service/issue_extensions.rb +13 -0
  78. data/lib/ecfr/testing/extensions/renderer_service/origin_extensions.rb +13 -0
  79. data/lib/ecfr/testing/extensions/search_service/content_version_result_extensions.rb +16 -0
  80. data/lib/ecfr/testing/extensions/search_service/date_facet_extensions.rb +13 -0
  81. data/lib/ecfr/testing/extensions/versioner_service/ancestors_extensions.rb +20 -0
  82. data/lib/ecfr/testing/extensions/versioner_service/title_extenstions.rb +16 -0
  83. data/lib/ecfr/testing/factories/admin_service/cfr_reference_factory.rb +14 -0
  84. data/lib/ecfr/testing/factories/admin_service/ecfr_correction_factory.rb +31 -0
  85. data/lib/ecfr/testing/factories/admin_service/issue_change_factory.rb +12 -0
  86. data/lib/ecfr/testing/factories/admin_service/issue_factory.rb +21 -0
  87. data/lib/ecfr/testing/factories/common/hierarchy_factory.rb +36 -0
  88. data/lib/ecfr/testing/factories/renderer_service/origin_factory.rb +32 -0
  89. data/lib/ecfr/testing/factories/search_service/content_version_count_factory.rb +20 -0
  90. data/lib/ecfr/testing/factories/search_service/content_version_result_factory.rb +76 -0
  91. data/lib/ecfr/testing/factories/search_service/date_facet_factory.rb +12 -0
  92. data/lib/ecfr/testing/factories/versioner_service/ancestors_factory.rb +26 -0
  93. data/lib/ecfr/testing/factories/versioner_service/metadata_node_info_factory.rb +15 -0
  94. data/lib/ecfr/testing/factories/versioner_service/node_summary_factory.rb +16 -0
  95. data/lib/ecfr/testing/factories/versioner_service/structure_factory.rb +57 -0
  96. data/lib/ecfr/testing/factories/versioner_service/title_factory.rb +36 -0
  97. data/lib/ecfr/testing/factory_bot_helpers/content_version.rb +38 -0
  98. data/lib/ecfr/testing/factory_bot_helpers/ecfr_gem_initialize_helpers.rb +51 -0
  99. data/lib/ecfr/testing/helpers/response_helper.rb +5 -0
  100. data/lib/ecfr/testing/strategies/ecfr_attribute_hash_strategy.rb +37 -0
  101. data/lib/ecfr/testing.rb +28 -0
  102. data/lib/ecfr/version.rb +5 -0
  103. data/lib/ecfr/versioner_service/ancestors/metadata_node_info.rb +22 -0
  104. data/lib/ecfr/versioner_service/ancestors/node_summary.rb +54 -0
  105. data/lib/ecfr/versioner_service/ancestors.rb +152 -0
  106. data/lib/ecfr/versioner_service/api_documentation.rb +7 -0
  107. data/lib/ecfr/versioner_service/base.rb +24 -0
  108. data/lib/ecfr/versioner_service/status.rb +7 -0
  109. data/lib/ecfr/versioner_service/structure.rb +120 -0
  110. data/lib/ecfr/versioner_service/title.rb +78 -0
  111. data/lib/ecfr/versioner_service/xml_content.rb +59 -0
  112. data/lib/ecfr.rb +90 -0
  113. data/lib/yard/attribute_handler.rb +87 -0
  114. data/lib/yard/metadata_handler.rb +87 -0
  115. metadata +389 -0
@@ -0,0 +1,7 @@
1
+ module Ecfr
2
+ module OfrProfileService
3
+ class Status < Base
4
+ include DefaultStatusSetup
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,33 @@
1
+ module Ecfr
2
+ module ParallelClient
3
+ module ClassMethods
4
+ def parallel_client(base_url:, client_options: {})
5
+ client(
6
+ base_url: base_url,
7
+ client_options: client_options.merge({adapter: :typhoeus})
8
+ )
9
+ end
10
+
11
+ # currently only handles expected cases when calling -renderer
12
+ def parallel_get(requests, client)
13
+ client.in_parallel do
14
+ requests.each do |request|
15
+ request.response = client.get(request.path, request.args) do |req|
16
+ Ecfr.config.request_hook.call(req)
17
+ end
18
+ end
19
+ end
20
+
21
+ requests
22
+ end
23
+ end
24
+
25
+ def self.included(base)
26
+ # only include parallel client if the appropriate adapter is present
27
+
28
+ Gem::Specification.find_by_name("faraday-typhoeus")
29
+ base.extend(ClassMethods)
30
+ rescue Gem::LoadError
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,17 @@
1
+ module Ecfr
2
+ module PrinceXmlService
3
+ class Base < Ecfr::Base
4
+ require_relative "pdf"
5
+
6
+ SERVICE_PATH = ""
7
+
8
+ def self.base_url
9
+ Ecfr.config.prince_xml_service_url
10
+ end
11
+
12
+ def self.service_name
13
+ "PrinceXML Service"
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,31 @@
1
+ module Ecfr
2
+ module PrinceXmlService
3
+ class Pdf < Base
4
+ class PdfGenerationFailure < StandardError; end
5
+
6
+ PDF_PATH = ""
7
+
8
+ def self.client(client_options = {}, &block)
9
+ client_options[:timeout] = Ecfr.config.prince_xml_service_pdf_timeout
10
+ super(client_options: client_options, &block)
11
+ end
12
+
13
+ def self.html_to_pdf(string, output_path, sha)
14
+ response = post(PDF_PATH, {html: string})
15
+
16
+ if response.status != 200 || response.body.blank?
17
+ raise PdfGenerationFailure.new(response.body)
18
+ else
19
+ directory = File.dirname(output_path)
20
+ FileUtils.mkdir_p(directory) unless File.directory?(directory)
21
+
22
+ # re Encoding::UndefinedConversionError ("\xE2" from ASCII-8BIT to UTF-8)
23
+ # faraday force_encoding https://github.com/lostisland/faraday/issues/139
24
+ File.write(output_path, response.body.force_encoding("utf-8"))
25
+
26
+ File.write(File.join(directory, "#{sha}.digest"), output_path)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,31 @@
1
+ module Ecfr
2
+ module RendererService
3
+ class Base < Ecfr::Base
4
+ require_relative "status"
5
+
6
+ require_relative "content"
7
+ require_relative "diff"
8
+ require_relative "origin"
9
+
10
+ SERVICE_PATH = "/api/renderer"
11
+
12
+ def self.base_url
13
+ Ecfr.config.renderer_service_url || Ecfr.config.base_url
14
+ end
15
+
16
+ def self.service_name
17
+ "Renderer Service"
18
+ end
19
+
20
+ # used by Content and Diff subclasses
21
+ HTML_PATH = "v1/content"
22
+
23
+ # content retrieval doesn't make use of the .perform worflow
24
+ # and so we need to add service path here.
25
+ def self.content_path(date, title_number, view_mode: "enhanced")
26
+ "#{SERVICE_PATH}/#{HTML_PATH}/#{view_mode}/#{date}/title-#{title_number}"
27
+ end
28
+ private_class_method :content_path
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,34 @@
1
+ module Ecfr
2
+ module RendererService
3
+ class Content < Base
4
+ attribute :html,
5
+ desc: "HTML document"
6
+
7
+ def self.find(date, hierarchy, options = {}, client_options: {})
8
+ hierarchy = hierarchy.dup
9
+
10
+ title = hierarchy.delete(:title)
11
+ view_mode = options.delete(:view_mode)
12
+
13
+ complete = hierarchy.delete(:complete)
14
+ options[:skip_ancestry_checks] = 1 if complete
15
+
16
+ new(
17
+ {
18
+ html: get(
19
+ content_path(date, title, view_mode: view_mode),
20
+ hierarchy.merge(options),
21
+ client_options
22
+ ).body
23
+ }.stringify_keys
24
+ )
25
+ end
26
+
27
+ def self.url_for(date, title_number, view_mode = "enhanced", params = {})
28
+ path = content_path(date, title_number, view_mode: view_mode)
29
+
30
+ client.build_url(path, params).to_s
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,31 @@
1
+ module Ecfr
2
+ module RendererService
3
+ class Diff < Base
4
+ class Undiffable < StandardError; end
5
+ class DiffTooLarge < Undiffable; end
6
+
7
+ attribute :html,
8
+ desc: "HTML document with inline changes"
9
+
10
+ def self.compare(old_date:, new_date:, hierarchy: {}, options: {})
11
+ hierarchy = hierarchy.dup
12
+
13
+ complete = hierarchy.delete(:complete)
14
+ options[:skip_ancestry_checks] = 1 if complete
15
+
16
+ title = hierarchy.delete(:title)
17
+ view_mode = options.delete(:view_mode) || :enhanced
18
+ params = hierarchy.merge(options).merge(compare: old_date)
19
+
20
+ new(
21
+ {
22
+ html: get(
23
+ content_path(new_date, title, view_mode: view_mode),
24
+ params
25
+ ).body
26
+ }.stringify_keys
27
+ )
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,56 @@
1
+ module Ecfr
2
+ module RendererService
3
+ #
4
+ # The Origin API endpoint returns metadata about Source and
5
+ # Authority for the requested hierachy. This metadata is
6
+ # provided for each part in the requested hierarchy as well as
7
+ # any subpart and section/appendix if there are any specific
8
+ # to those levels.
9
+ #
10
+ class Origin < Base
11
+ result_key :origins
12
+
13
+ attribute :identifier,
14
+ desc: "the identifier portion of the level (ex. '121')"
15
+ attribute :label_level,
16
+ desc: "the full identifier and label (ex. 'Part 121')"
17
+ attribute :level,
18
+ desc: "the level being represented (ex. 'part')"
19
+ attribute :link,
20
+ desc: "the path portion of a link to the content for the hierarchy provided"
21
+ attribute :title,
22
+ desc: "the description of the level"
23
+
24
+ attribute :authority,
25
+ type: Array(:string),
26
+ desc: "the Authority content for the level"
27
+ attribute :source,
28
+ type: Array(:string),
29
+ desc: "the Source content for the level"
30
+
31
+ attribute :current,
32
+ type: :boolean,
33
+ desc: "whether this is the hierarchy level requested (instead of an ancestor or descendant)"
34
+
35
+ attribute :hierarchy,
36
+ type: Ecfr::Common::Hierarchy
37
+
38
+ ORIGIN_PATH = "v1/origin"
39
+
40
+ def self.find(date, title_number, params = {})
41
+ supported_params = (Ecfr::Constants::Hierarchy::HIERARCHY_LEVELS[1, 8] + [:build_id]).map(&:to_sym)
42
+
43
+ perform(
44
+ :get,
45
+ origin_path(date, title_number),
46
+ params: params.slice(*supported_params)
47
+ )
48
+ end
49
+
50
+ def self.origin_path(date, title_number)
51
+ "#{ORIGIN_PATH}/#{date}/title-#{title_number}"
52
+ end
53
+ private_class_method :origin_path
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,7 @@
1
+ module Ecfr
2
+ module RendererService
3
+ class Status < Base
4
+ include DefaultStatusSetup
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,12 @@
1
+ module Ecfr
2
+ class RequestRepresentation
3
+ attr_reader :args, :options, :path
4
+ attr_accessor :response
5
+
6
+ def initialize(path:, args:, options: {})
7
+ @args = args
8
+ @options = options
9
+ @path = path
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,7 @@
1
+ module Ecfr
2
+ module SearchService
3
+ class ApiDocumentation < Base
4
+ include DefaultDocumentationSetup
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,23 @@
1
+ module Ecfr
2
+ module SearchService
3
+ class Base < Ecfr::Base
4
+ require_relative "api_documentation"
5
+ require_relative "status"
6
+
7
+ require_relative "content_version"
8
+ require_relative "facet_base"
9
+ require_relative "date_facet"
10
+ require_relative "title_facet"
11
+
12
+ SERVICE_PATH = "/api/search"
13
+
14
+ def self.base_url
15
+ Ecfr.config.search_service_url || Ecfr.config.base_url
16
+ end
17
+
18
+ def self.service_name
19
+ "Search Service"
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,33 @@
1
+ module Ecfr
2
+ module SearchService
3
+ class ContentVersion
4
+ class Count < Base
5
+ metadata_key :meta
6
+
7
+ metadata :description,
8
+ desc: "description of the search results"
9
+
10
+ metadata :total_count,
11
+ type: :integer,
12
+ desc: "total count of matching results"
13
+
14
+ COUNT_PATH = "v1/count"
15
+
16
+ #
17
+ # Retrieves count metadata about a search query
18
+ #
19
+ # @param [<Hash>] args see: {Ecfr::SearchService::ContentVersion::Result.find}
20
+ #
21
+ # @return [<Count>] search query count metadata
22
+ #
23
+ def self.find(args)
24
+ perform(
25
+ :get,
26
+ COUNT_PATH,
27
+ params: args
28
+ )
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,17 @@
1
+ module Ecfr
2
+ module SearchService
3
+ class ContentVersion
4
+ class HierarchicalCount
5
+ include AttributeMethodDefinition
6
+ extend Extensible
7
+
8
+ attribute :relation,
9
+ desc: "currently will always return 'eq'"
10
+
11
+ attribute :value,
12
+ type: :integer,
13
+ desc: "total number of matching results"
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,30 @@
1
+ module Ecfr
2
+ module SearchService
3
+ class ContentVersion
4
+ class HierarchicalCountNode
5
+ include AttributeMethodDefinition
6
+ extend Extensible
7
+
8
+ attribute :heading,
9
+ desc: "the descriptive portion of the CFR level"
10
+ attribute :hierarchy,
11
+ desc: "the shortened identifier portion of the CFR level"
12
+ attribute :hierarchy_heading,
13
+ desc: "the full identifier portion of the CFR level"
14
+ attribute :level,
15
+ desc: "current hierarchy level"
16
+
17
+ attribute :count,
18
+ type: :integer,
19
+ desc: "number of results this hierarchy level contains"
20
+ attribute :max_score,
21
+ type: :integer,
22
+ desc: "the max score of any result this hierarchy level contains"
23
+
24
+ attribute :children,
25
+ type: Array(HierarchicalCountNode),
26
+ desc: "An array of counts data for the next level down in the hierarchy"
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,42 @@
1
+ module Ecfr
2
+ module SearchService
3
+ class ContentVersion
4
+ class HierarchicalResult < Ecfr::SearchService::ContentVersion
5
+ require_relative "hierarchical_count"
6
+ require_relative "hierarchical_count_node"
7
+
8
+ attribute :count,
9
+ type: HierarchicalCount
10
+
11
+ attribute :max_score,
12
+ type: :float,
13
+ desc: "maximum score for all results"
14
+
15
+ attribute :shown_count,
16
+ type: :integer,
17
+ desc: "number of results returned"
18
+
19
+ attribute :children,
20
+ type: Array(HierarchicalCountNode),
21
+ desc: "recursive array of results nested in their hierarchy"
22
+
23
+ HIERARCHICAL_COUNTS_PATH = "v1/counts/hierarchy"
24
+
25
+ #
26
+ # Retrieves results of search query as a hierarchical structure
27
+ #
28
+ # @param [<Hash>] args see: {Ecfr::SearchService::ContentVersion::Result.find}
29
+ #
30
+ # @return [<HierarchicalResult>]
31
+ #
32
+ def self.find(args)
33
+ perform(
34
+ :get,
35
+ HIERARCHICAL_COUNTS_PATH,
36
+ params: args
37
+ )
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,110 @@
1
+ module Ecfr
2
+ module SearchService
3
+ class ContentVersion
4
+ class Result < Ecfr::SearchService::ContentVersion
5
+ extend Ecfr::Constants::ChangeTypes
6
+
7
+ result_key :results
8
+
9
+ attribute :full_text_excerpt,
10
+ desc: "excerpt of text where matching keywords were found"
11
+ attribute :type,
12
+ desc: "the content type returned - either *Appendix* or *Section*"
13
+
14
+ attribute :score,
15
+ type: :float,
16
+ desc: "numerical representation of ranking within results"
17
+
18
+ attribute :structure_index,
19
+ type: :integer,
20
+ desc: "index of where content was found in relation to other content within the title - used for sorting"
21
+
22
+ attribute :change_types,
23
+ type: Array(:symbol),
24
+ desc: "array of change types that corresponds to the keys in {KNOWN_CHANGE_TYPES}"
25
+
26
+ attribute :reserved,
27
+ type: :boolean,
28
+ desc: "whether the result represents a portion of the CFR that is Reserved"
29
+ attribute :removed,
30
+ type: :boolean,
31
+ desc: "whether the result represents a part of the CFR that has been removed"
32
+
33
+ attribute :ends_on,
34
+ type: :date,
35
+ desc: "the last date this version of the content appeared in the eCFR"
36
+ attribute :starts_on,
37
+ type: :date,
38
+ desc: "the first date this version of the content appeared in the eCFR"
39
+
40
+ attribute :headings,
41
+ type: Ecfr::Common::Hierarchy,
42
+ desc: "the descriptive portion of the CFR level"
43
+ attribute :hierarchy,
44
+ type: Ecfr::Common::Hierarchy,
45
+ desc: "the shortened identifier portion of the CFR level"
46
+ attribute :hierarchy_headings,
47
+ type: Ecfr::Common::Hierarchy,
48
+ desc: "the full identifier portion of the CFR level"
49
+
50
+ metadata_key :meta
51
+
52
+ metadata :description,
53
+ desc: "description of the search results"
54
+
55
+ metadata :current_page,
56
+ type: :integer,
57
+ desc: "current page of results"
58
+ metadata :total_count,
59
+ type: :integer,
60
+ desc: "total count of matching results across all pages"
61
+ metadata :total_pages,
62
+ type: :integer,
63
+ desc: "total number of pages in the search results"
64
+
65
+ metadata :max_score,
66
+ type: :float,
67
+ desc: "the maximum score of any match in the search"
68
+
69
+ # returned when pagingate_by=date option is provided
70
+ # as part of the search query
71
+ metadata :min_date,
72
+ type: :date,
73
+ desc: "the minimum date of all returned results"
74
+ # returned when pagingate_by=date option is provided
75
+ # as part of the search query
76
+ metadata :max_date,
77
+ type: :date,
78
+ desc: "the maximum date of all returned results"
79
+
80
+ RESULTS_PATH = "v1/results"
81
+
82
+ #
83
+ # Retrieve results of search query
84
+ #
85
+ # @param [<Hash>] args supported search parameters; supported params:
86
+ # @option args [String] :date ISO date or 'current'
87
+ # @option args [String] :last_modified_after ISO date
88
+ # @option args [String] :last_modified_before ISO date
89
+ # @option args [String] :last_modified_on_or_after ISO date
90
+ # @option args [String] :last_modified_on_or_before ISO date
91
+ # @option args [String] :order relevance, hierarchy, newest_first, oldest_first
92
+ # @option args [Integer] :page page of search results to return
93
+ # @option args [Integer] :per_page number of results per page
94
+ # @option args [String] :paginate_by one of "results" or "date"
95
+ # if "date" then one of the last_modified_* options is required
96
+ # @option args [String] :query search terms
97
+ #
98
+ # @return [[<Result>]] an array of results with metadata
99
+ #
100
+ def self.find(args)
101
+ perform(
102
+ :get,
103
+ RESULTS_PATH,
104
+ params: args.compact
105
+ )
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,76 @@
1
+ module Ecfr
2
+ module SearchService
3
+ class ContentVersion
4
+ class Suggestion < Ecfr::SearchService::ContentVersion
5
+ result_key :suggestions
6
+
7
+ attribute :highlight,
8
+ desc: "text containing '<mark>' tags the denote where the query was
9
+ found by the search engine - only type: 'autocomplete'"
10
+ # attribute :summary,
11
+ # desc: "description of the search - only type: 'cfr_reference'"
12
+ attribute :type,
13
+ desc: "the type of suggestion, currently 'cfr_reference' or 'autocomplete'"
14
+
15
+ attribute :reserved,
16
+ type: :boolean,
17
+ desc: "whether the result represents a portion of the CFR that is Reserved"
18
+
19
+ attribute :removed,
20
+ type: :date,
21
+ desc: "the date that this portion of the CFR that was removed"
22
+
23
+ attribute :structure_index,
24
+ type: :integer,
25
+ desc: "an integer representing the location of the suggestion in the
26
+ source xml for it's corresponding title. Useful for sorting. Only
27
+ type: 'autocomplete'"
28
+
29
+ attribute :headings,
30
+ type: Ecfr::Common::Hierarchy,
31
+ desc: "the descriptive portion of the CFR level"
32
+ attribute :hierarchy,
33
+ type: Ecfr::Common::Hierarchy,
34
+ desc: "the shortened identifier portion of the CFR level"
35
+ attribute :hierarchy_headings,
36
+ type: Ecfr::Common::Hierarchy,
37
+ desc: "the full identifier portion of the CFR level"
38
+
39
+ metadata_key :meta
40
+
41
+ metadata :global_search_results,
42
+ type: :integer,
43
+ desc: "total number of matching full text search results"
44
+ metadata :narrowed_search_results,
45
+ type: :integer,
46
+ desc: "number of search results that match the current hierarchy"
47
+
48
+ metadata :narrowed_hierarchy,
49
+ type: Ecfr::Common::Hierarchy,
50
+ desc: "the hierarchy that the narrowed_search_results have been limited to"
51
+
52
+ SUGGESTIONS_PATH = "v1/suggestions"
53
+
54
+ #
55
+ # Retreive suggestions for the given search args
56
+ #
57
+ # @param [<Hash>] args see: {Ecfr::SearchService::ContentVersion::Result.find}
58
+ # @option Boolean autocomplete Adding autocomplete=true to the request
59
+ # will return suggestions of type 'autocomplete'
60
+ #
61
+ # @return [<Suggestion>] by default suggestions will be of type
62
+ # 'cfr_reference' and are useful for refining full text search requests.
63
+ # When autocomplete=true has been added to the request then suggestions
64
+ # will be of type 'autocomplete' and are useful for typeahead style searches
65
+ #
66
+ def self.find(args)
67
+ perform(
68
+ :get,
69
+ SUGGESTIONS_PATH,
70
+ params: args.except(:order, :page, :per_page).stringify_keys
71
+ )
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,27 @@
1
+ module Ecfr
2
+ module SearchService
3
+ class ContentVersion
4
+ class Summary < Ecfr::SearchService::ContentVersion
5
+ metadata_key :meta
6
+
7
+ metadata :description
8
+
9
+ SUMMARY_PATH = "v1/summary"
10
+
11
+ #
12
+ # <Description>
13
+ #
14
+ # @param [<Hash>] args search arguments - everthing except
15
+ # :hierarchy and :query will be removed
16
+ #
17
+ # @return [<Summary>]
18
+ #
19
+ def self.find(args)
20
+ valid_args = args.slice(:hierarchy, :query)
21
+
22
+ perform(:get, SUMMARY_PATH, params: valid_args)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end