dvla-kaping 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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: '09c60001616143c58b1a1023bea7cdcf653c495ea1410222d767dc8718f4507b'
4
+ data.tar.gz: 713da1c1de593ffc1e516aa4a0727eae53174fb6832f82a0b8c988b7bba85ff7
5
+ SHA512:
6
+ metadata.gz: f182de8966f9e515888ccfc137322df7a1fb8a84e7949ae2a9d8d439ff2ecc3f9f464766d864de3d7ec2480d3f965541be74e80af92e9eb8fc7b4d26ded224c0
7
+ data.tar.gz: 25a74e3a85a3719952e7239919b53aa1e6a9294713ece79c60ee42cce20ac862fb9b4487e65ceed33661f731dad97f1fdaaacd5256b968aab9fe69fb6f3a63cf
data/.drone.yml ADDED
@@ -0,0 +1,122 @@
1
+ ---
2
+ kind: pipeline
3
+ name: audit, lint & test
4
+
5
+ platform:
6
+ os: linux
7
+ arch: amd64
8
+
9
+ trigger:
10
+ event:
11
+ - push
12
+
13
+ drone_cache_image: &drone_cache_image
14
+ image: 448934085854.dkr.ecr.eu-west-2.amazonaws.com/ce/drone-cache
15
+ pull: if-not-exists
16
+
17
+ drone_cache_settings: &drone_cache_settings
18
+ bucket: dvla-drone1-cache-448934085854
19
+ region: eu-west-2
20
+ encryption: AES256
21
+ endpoint: https://s3.eu-west-2.amazonaws.com
22
+
23
+ gem_cache_mount: &gem_cache_mount
24
+ mount:
25
+ - vendor/bundle
26
+
27
+ ruby_image: &ruby_image
28
+ image: 448934085854.dkr.ecr.eu-west-2.amazonaws.com/base-images/qe-ruby:3
29
+
30
+ sonar_image: &sonar_image
31
+ image: 448934085854.dkr.ecr.eu-west-2.amazonaws.com/utilities-ci-tools/ci-drone-sonar-scanner
32
+ pull: if-not-exists
33
+
34
+ steps:
35
+ - name: Restore gems from cache
36
+ <<: *drone_cache_image
37
+ settings:
38
+ <<: *drone_cache_settings
39
+ <<: *gem_cache_mount
40
+ restore: true
41
+ cache_key: '{{ checksum "./README.md" }}' # Override the README to force a rebuild
42
+
43
+ - name: Unit tests
44
+ <<: *ruby_image
45
+ depends_on:
46
+ - Restore gems from cache
47
+ commands:
48
+ - bundle install
49
+ - bundle exec rspec
50
+ environment:
51
+ BUNDLE_PATH: vendor/bundle
52
+
53
+ - name: Gem audit
54
+ <<: *ruby_image
55
+ depends_on:
56
+ - Restore gems from cache
57
+ commands:
58
+ - bundle install
59
+ - bundle exec bundle-audit
60
+ environment:
61
+ BUNDLE_PATH: vendor/bundle
62
+
63
+ - name: Lint
64
+ <<: *ruby_image
65
+ depends_on:
66
+ - Restore gems from cache
67
+ commands:
68
+ - bundle install
69
+ - bundle exec rubocop
70
+ environment:
71
+ BUNDLE_PATH: vendor/bundle
72
+
73
+ - name: SonarQube
74
+ <<: *sonar_image
75
+ depends_on:
76
+ - Unit tests
77
+
78
+ - name: Build and deploy (dry-run)
79
+ image: 448934085854.dkr.ecr.eu-west-2.amazonaws.com/utilities-ci-tools/ci-qe-deploy-gem:latest
80
+ commands:
81
+ - git fetch origin main
82
+ - app
83
+ environment:
84
+ DRYRUN: true
85
+ VERBOSE: false
86
+
87
+ - name: Rebuild gem cache
88
+ <<: *drone_cache_image
89
+ depends_on:
90
+ - Gem audit
91
+ settings:
92
+ <<: *drone_cache_settings
93
+ <<: *gem_cache_mount
94
+ rebuild: true
95
+ cache_key: '{{ checksum "./README.md" }}'
96
+
97
+ ---
98
+ kind: pipeline
99
+ name: deploy
100
+ depends_on:
101
+ - audit, lint & test
102
+
103
+ platform:
104
+ os: linux
105
+ arch: amd64
106
+
107
+ trigger:
108
+ event:
109
+ - push
110
+ branch:
111
+ - main
112
+
113
+ steps:
114
+ - name: Build and deploy
115
+ image: 448934085854.dkr.ecr.eu-west-2.amazonaws.com/utilities-ci-tools/ci-qe-deploy-gem:latest
116
+ commands:
117
+ - git fetch origin main
118
+ - app
119
+ environment:
120
+ DRYRUN: false
121
+ VERBOSE: false
122
+
data/.rspec ADDED
@@ -0,0 +1,6 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
4
+ --format RspecSonarqubeFormatter
5
+ --out out/test-report.xml
6
+
data/.rubocop.yml ADDED
@@ -0,0 +1,11 @@
1
+ inherit_gem:
2
+ dvla-lint: ".rubocop.yml"
3
+
4
+ AllCops:
5
+ TargetRubyVersion: 3.0
6
+
7
+ Style/StringLiterals:
8
+ EnforcedStyle: single_quotes
9
+
10
+ Style/StringLiteralsInInterpolation:
11
+ EnforcedStyle: single_quotes
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.4.2
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2024-11-07
4
+
5
+ - Initial release
@@ -0,0 +1,132 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We as members, contributors, and leaders pledge to make participation in our
6
+ community a harassment-free experience for everyone, regardless of age, body
7
+ size, visible or invisible disability, ethnicity, sex characteristics, gender
8
+ identity and expression, level of experience, education, socio-economic status,
9
+ nationality, personal appearance, race, caste, color, religion, or sexual
10
+ identity and orientation.
11
+
12
+ We pledge to act and interact in ways that contribute to an open, welcoming,
13
+ diverse, inclusive, and healthy community.
14
+
15
+ ## Our Standards
16
+
17
+ Examples of behavior that contributes to a positive environment for our
18
+ community include:
19
+
20
+ * Demonstrating empathy and kindness toward other people
21
+ * Being respectful of differing opinions, viewpoints, and experiences
22
+ * Giving and gracefully accepting constructive feedback
23
+ * Accepting responsibility and apologizing to those affected by our mistakes,
24
+ and learning from the experience
25
+ * Focusing on what is best not just for us as individuals, but for the overall
26
+ community
27
+
28
+ Examples of unacceptable behavior include:
29
+
30
+ * The use of sexualized language or imagery, and sexual attention or advances of
31
+ any kind
32
+ * Trolling, insulting or derogatory comments, and personal or political attacks
33
+ * Public or private harassment
34
+ * Publishing others' private information, such as a physical or email address,
35
+ without their explicit permission
36
+ * Other conduct which could reasonably be considered inappropriate in a
37
+ professional setting
38
+
39
+ ## Enforcement Responsibilities
40
+
41
+ Community leaders are responsible for clarifying and enforcing our standards of
42
+ acceptable behavior and will take appropriate and fair corrective action in
43
+ response to any behavior that they deem inappropriate, threatening, offensive,
44
+ or harmful.
45
+
46
+ Community leaders have the right and responsibility to remove, edit, or reject
47
+ comments, commits, code, wiki edits, issues, and other contributions that are
48
+ not aligned to this Code of Conduct, and will communicate reasons for moderation
49
+ decisions when appropriate.
50
+
51
+ ## Scope
52
+
53
+ This Code of Conduct applies within all community spaces, and also applies when
54
+ an individual is officially representing the community in public spaces.
55
+ Examples of representing our community include using an official email address,
56
+ posting via an official social media account, or acting as an appointed
57
+ representative at an online or offline event.
58
+
59
+ ## Enforcement
60
+
61
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
62
+ reported to the community leaders responsible for enforcement at
63
+ [INSERT CONTACT METHOD].
64
+ All complaints will be reviewed and investigated promptly and fairly.
65
+
66
+ All community leaders are obligated to respect the privacy and security of the
67
+ reporter of any incident.
68
+
69
+ ## Enforcement Guidelines
70
+
71
+ Community leaders will follow these Community Impact Guidelines in determining
72
+ the consequences for any action they deem in violation of this Code of Conduct:
73
+
74
+ ### 1. Correction
75
+
76
+ **Community Impact**: Use of inappropriate language or other behavior deemed
77
+ unprofessional or unwelcome in the community.
78
+
79
+ **Consequence**: A private, written warning from community leaders, providing
80
+ clarity around the nature of the violation and an explanation of why the
81
+ behavior was inappropriate. A public apology may be requested.
82
+
83
+ ### 2. Warning
84
+
85
+ **Community Impact**: A violation through a single incident or series of
86
+ actions.
87
+
88
+ **Consequence**: A warning with consequences for continued behavior. No
89
+ interaction with the people involved, including unsolicited interaction with
90
+ those enforcing the Code of Conduct, for a specified period of time. This
91
+ includes avoiding interactions in community spaces as well as external channels
92
+ like social media. Violating these terms may lead to a temporary or permanent
93
+ ban.
94
+
95
+ ### 3. Temporary Ban
96
+
97
+ **Community Impact**: A serious violation of community standards, including
98
+ sustained inappropriate behavior.
99
+
100
+ **Consequence**: A temporary ban from any sort of interaction or public
101
+ communication with the community for a specified period of time. No public or
102
+ private interaction with the people involved, including unsolicited interaction
103
+ with those enforcing the Code of Conduct, is allowed during this period.
104
+ Violating these terms may lead to a permanent ban.
105
+
106
+ ### 4. Permanent Ban
107
+
108
+ **Community Impact**: Demonstrating a pattern of violation of community
109
+ standards, including sustained inappropriate behavior, harassment of an
110
+ individual, or aggression toward or disparagement of classes of individuals.
111
+
112
+ **Consequence**: A permanent ban from any sort of public interaction within the
113
+ community.
114
+
115
+ ## Attribution
116
+
117
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118
+ version 2.1, available at
119
+ [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
120
+
121
+ Community Impact Guidelines were inspired by
122
+ [Mozilla's code of conduct enforcement ladder][Mozilla CoC].
123
+
124
+ For answers to common questions about this code of conduct, see the FAQ at
125
+ [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
126
+ [https://www.contributor-covenant.org/translations][translations].
127
+
128
+ [homepage]: https://www.contributor-covenant.org
129
+ [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
130
+ [Mozilla CoC]: https://github.com/mozilla/diversity
131
+ [FAQ]: https://www.contributor-covenant.org/faq
132
+ [translations]: https://www.contributor-covenant.org/translations
data/LICENCE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 HM Government (Driver and Vehicle Licensing Agency)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,145 @@
1
+ # Dvla::Kaping
2
+
3
+ The Kaping! gem - an idiomatic way to create DSL openSearch definitions
4
+
5
+ ## OpenSearch Query DSL
6
+ https://opensearch.org/docs/latest/query-dsl/
7
+
8
+ OpenSearch provides a search language called Query domain-specific language (Query DSL) that you can use to search for data.
9
+ Query DSL is a flexible language with a JSON interface.
10
+
11
+ With query DSL, you need to specify a query in the query parameter of the search. One of the simplest searches in OpenSearch
12
+ uses the match_all query, which matches all documents in an index:
13
+
14
+ ```ruby
15
+ {"query":{"match_all":{}}}
16
+ ```
17
+ Or you can search on a specific field for a specif value
18
+
19
+ ```ruby
20
+ {
21
+ "query": {
22
+ "match_phrase": { "foo":"BAR"} }
23
+ }
24
+ ```
25
+
26
+ The real power of OpenSearch is you can combine multiple queries clauses to build complex search queries, the problem is they can be
27
+ complex to construct so this gem looks to simplify the process.
28
+
29
+ ## Query and filter context
30
+
31
+ A filter context asks - “Does the document match the query clause?” and returns matching documents
32
+ i.e it's a binary answer
33
+
34
+ A query context asks - “How well does the document match the query clause?”, - also returns a relevance score
35
+ good for full-text searches
36
+
37
+ # How to use
38
+ Before you can use this Gem please ensure you can access your Opensearch instance. You may
39
+ also need to configure and assume an AWS role depending on your environment
40
+
41
+ The **query builder** is the main feature of this gem, but there is also the additional facility to
42
+ set up a **client** to send the queries to OpeSearch. The client is also extended to enable a **search**,
43
+ so this will spin up a client, connect and post the query. Each of these features can be used independently
44
+ or as a package.
45
+
46
+ ## Configuration
47
+
48
+ The gem makes use of the config settings to target the different environments, tables and aws settings.
49
+
50
+ The setting can be over-written by adding this line in your code
51
+
52
+ ```ruby
53
+ DVLA::Kaping.configure { |attr| attr.yaml_override_path = './config/kaping.yml' }
54
+
55
+ ```
56
+ The 'index' setting will control what environment to target
57
+
58
+ The 'result_size' setting determines how many records to be returned from the query, if you are doing a post query filtering
59
+ code side then you should pump this value up.
60
+
61
+ ```yml
62
+ kaping:
63
+ host: <%= ENV['HOST'] || 'https://[path-to-service]' %>
64
+ index: <%= ENV['INDEX'] || 'index-name' %>
65
+ result_size: 50
66
+ log_level: <%= ENV['LOG_LEVEL'] || :debug %>
67
+
68
+ ```
69
+ If you want to use the built-in client, and your OpenSearch instance is hosted in a Amazon VPC you will need to assume AWS permissions for access to run the queries.
70
+ there are two options, you can either use profile or environment
71
+
72
+ Profile will just pick up the credentials save in your specified shared credentials ini file at ~/.aws/credentials,
73
+
74
+ ```yml
75
+ aws:
76
+ # to use a AWS profile config file then set to profile, otherwise environment settings will be used
77
+ credential_type: profile
78
+ account_id: ##########
79
+ region: aws-region
80
+ profile: PROFILE
81
+ role: ROLE
82
+ ```
83
+
84
+ ## Client
85
+
86
+ client.connect will get you a new connection to use for a search query. The client is fully configurable from the config settings.
87
+
88
+ ```ruby
89
+ client = DVLA::Kaping::AWSClient.new
90
+ con = client.connect
91
+ ```
92
+
93
+ The client will need credentials which can be configured in the kaping.yml file or set as environment variables
94
+
95
+ ## Search
96
+ You can use the shortcut search facility, this will connect to a new client, all you need to do is supply the query to run
97
+
98
+ ```ruby
99
+ body = DVLA::Kaping::Query.new('bool')
100
+ body.filter.term('foo.bar', 'Valid').
101
+ between('foo.dateOfBirth', '1958-08-21', '1970-08-21')
102
+
103
+ response = DVLA::Kaping.search(body)
104
+ ```
105
+
106
+ ## Query building
107
+ A query can be built up with dot notation, but there are a few rules to follow.
108
+
109
+ First get a new Kaping Query instance. If we want a new Boolean query then we set the type as bool.
110
+ ```ruby
111
+ my_query = DVLA::Kaping::Query.new('bool')
112
+ my_query.filter.term('foo.bar', 'Valid').
113
+ between('foo.bar', '1958-08-21', '1970-08-21')
114
+
115
+ # my_query.to_json will produce
116
+ '{"query":{"bool":{"filter":[{"term":{"foo":"Valid"}},{"range":{"foo.bar":{"gte":"1958-08-21","lte":"1970-08-21"}}}]}}}'
117
+
118
+ # we don't have to set the type for simple queries, the second parameter takes in key word arguments
119
+
120
+ my_query = DVLA::Kaping::Query.new('match_phrase', foo: 'bar')
121
+
122
+ # my_query.to_json will produce
123
+ '{"query":{"match_phrase":{"foo":"bar"}}}'
124
+ ```
125
+
126
+ We then set the context of the query, this can be in the form of a filter context or a query context parameter.
127
+
128
+ ```ruby
129
+ my_query.filter
130
+ # or
131
+ my_query.match
132
+ ```
133
+
134
+ Then you can start building up complex queries as required with the dot notation
135
+
136
+ Once you have your query defined you will then need to call to_json to build out the full
137
+ structure of the query from the ruby objects.
138
+
139
+ ```ruby
140
+ my_json_query = my_query.to_json
141
+ ```
142
+
143
+ ## Code of Conduct
144
+
145
+ Everyone interacting in the Dvla::Kaping project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](./CODE_OF_CONDUCT.md).
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require 'rubocop/rake_task'
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
data/config/kaping.yml ADDED
@@ -0,0 +1,4 @@
1
+ kaping_host: <%= ENV['KAPING_HOST'] || 'nil' %>
2
+ kaping_index: <%= ENV['KAPING_INDEX'] || 'nil' %>
3
+ kaping_result_size: <%= ENV['KAPING_RESULT_SIZE'] || '50' %>
4
+ kaping_log_level: <%= ENV['KAPING_LOG_LEVEL'] || :debug %>
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'aws-sdk-core'
4
+ require 'opensearch-aws-sigv4'
5
+ require 'aws-sigv4'
6
+
7
+ module DVLA
8
+ module Kaping
9
+ class AWSClient
10
+ def initialize
11
+ @base_url = Kaping.yaml[:kaping_host]
12
+ @aws_account_id = Kaping.yaml.dig(:aws, :account_id)
13
+ @role = Kaping.yaml.dig(:aws, :role)
14
+ @region = Kaping.yaml.dig(:aws, :region)
15
+ Kaping.logger.info { "Kaping Client | base_url: '#{@base_url}'" }
16
+ end
17
+
18
+ def connect
19
+ credentials = if Kaping.yaml.dig(:aws, :credential_type) == 'profile'
20
+ assume_role_profile(@aws_account_id, @role)
21
+ else
22
+ assume_role_env(@aws_account_id, @role)
23
+ end
24
+
25
+ signer = Aws::Sigv4::Signer.new(service: 'es',
26
+ region: @region,
27
+ credentials_provider: credentials)
28
+
29
+ OpenSearch::Aws::Sigv4Client.new({
30
+ host: @base_url,
31
+ log: false,
32
+ }, signer)
33
+ end
34
+
35
+ private
36
+
37
+ # @returns aws credentials using a profile
38
+ def assume_role_profile(aws_account_id, role)
39
+ role_arn = "arn:aws:iam::#{aws_account_id}:role/#{role}"
40
+ sts = Aws::STS::Client.new(region: @region, profile: Kaping.yaml.dig(:aws, :profile))
41
+ sts.assume_role(role_arn: role_arn, role_session_name: 'kaping')
42
+ rescue Aws::STS::Errors::ServiceError => e
43
+ raise "#{__method__}: AWS Profile Credentials Issue: #{e.message} #{e.class.name}"
44
+ end
45
+
46
+ # via ENV settings - these are pick up directly
47
+ def assume_role_env(aws_account_id, role)
48
+ role_arn = "arn:aws:iam::#{aws_account_id}:role/#{role}"
49
+ sts = Aws::STS::Client.new(region: @region)
50
+ sts.assume_role(role_arn: role_arn, role_session_name: 'kaping')
51
+ rescue Aws::STS::Errors::ServiceError => e
52
+ raise "#{__method__}: AWS ENV Credentials Issue: #{e.message} #{e.class.name}"
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+ require 'active_support/core_ext/hash/deep_merge'
5
+
6
+
7
+ module DVLA
8
+ module Kaping
9
+ class Config
10
+ attr_accessor :yaml
11
+ attr_reader :logger, :yaml_override_path
12
+
13
+ ATTRIBUTES = %w[host index result_size log_level].freeze
14
+ attr_accessor(*ATTRIBUTES)
15
+
16
+ def initialize
17
+ @yaml = load_yaml(Dir["#{Dir.pwd}/**/kaping.yml"].first)
18
+
19
+ ATTRIBUTES.each do |attr|
20
+ instance_variable_set(:"@#{attr}", ENV.fetch(attr.to_s.upcase, nil))
21
+ end
22
+
23
+ @logger = Logger.new($stdout)
24
+ @log_level ||= 'INFO'
25
+ @result_size ||= 100
26
+ end
27
+
28
+ # step 1 - find kaping.yml
29
+ def load_yaml(path)
30
+ path = "#{path}.yml" unless %w[.yaml .yml].include?(File.extname(path))
31
+
32
+ if File.exist?(path)
33
+ YAML.safe_load(ERB.new(File.read(path)).result, symbolize_names: true, aliases: true)
34
+ else
35
+ warn("[WARN] YAML file not found at: '#{path}'")
36
+ nil
37
+ end
38
+ end
39
+
40
+ def yaml_override_path=(path)
41
+ unless path == @yaml_override_path
42
+ @yaml_override_path = path
43
+
44
+ merge_yaml(@yaml_override_path)
45
+ end
46
+ end
47
+
48
+ def merge_yaml(path)
49
+ config = load_yaml(path)
50
+
51
+ @yaml = @yaml.deep_merge(config) unless config.nil?
52
+ end
53
+
54
+ def logger=(new_logger)
55
+ if new_logger.is_a?(Logger)
56
+ @logger = new_logger
57
+ @logger.level = @yaml[kaping_log_level]
58
+ else
59
+ warn("[WARN] Custom logger is not an instance of Logger: '#{new_logger.class}'")
60
+ end
61
+ end
62
+
63
+ def log_level(_log_level = @yaml.dig(:kaping, :log_level))
64
+ @log
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,41 @@
1
+ require_relative 'query_term'
2
+
3
+ module DVLA
4
+ module Kaping
5
+ class Query
6
+ include QueryTerm
7
+
8
+ BOOL = 'bool'.freeze
9
+ MATCH = 'match'.freeze
10
+
11
+ attr_reader :query_type, :kwargs
12
+ protected attr_reader :operations, :parameters
13
+ protected attr_accessor :last_operation
14
+
15
+ def initialize(type, **kwargs)
16
+ super()
17
+ @last_operation = nil
18
+ @query_type = type
19
+ @operations ||= {}
20
+ @kwargs = kwargs
21
+ end
22
+
23
+ # associated with Boolean query
24
+ %i[must must_not should filter].each do |op|
25
+ define_method(op) do
26
+ @last_operation = op
27
+ operations[op] ||= []
28
+ self
29
+ end
30
+ end
31
+
32
+ def to_json(*_args)
33
+ if kwargs.empty?
34
+ { query: { "#{query_type}": operations } }.to_json
35
+ else
36
+ { query: { "#{query_type}": kwargs } }.to_json
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,89 @@
1
+ # assumes @operation exists
2
+ # assumes @last_operation exists
3
+
4
+ module DVLA
5
+ module Kaping
6
+ module Constants
7
+ ALLOWED_PARAMS = :minimum_should_match
8
+ end
9
+
10
+ module QueryTerm
11
+ def match_phrase(field, value, **kwargs)
12
+ current_operation << {
13
+ match_phrase: { "#{field}": value },
14
+ }
15
+ current_params(kwargs)
16
+ self
17
+ end
18
+
19
+ def match(field, value)
20
+ current_operation << {
21
+ match: { "#{field}": value },
22
+ }
23
+ self
24
+ end
25
+
26
+ def exists(field, value)
27
+ current_operation << {
28
+ exists: { "#{field}": value },
29
+ }
30
+ self
31
+ end
32
+
33
+ def wildcard(field, value)
34
+ current_operation << {
35
+ wildcard: { "#{field}": value },
36
+ }
37
+ self
38
+ end
39
+
40
+ def term(field, value)
41
+ current_operation << {
42
+ term: { "#{field}": value },
43
+ }
44
+ self
45
+ end
46
+
47
+ def prefix(field, value)
48
+ current_operation << {
49
+ prefix: { "#{field}": value },
50
+ }
51
+ self
52
+ end
53
+
54
+ def regex(field, value)
55
+ current_operation << {
56
+ regex: { "#{field}": value },
57
+ }
58
+ self
59
+ end
60
+
61
+ def between(field, *args)
62
+ fragment = case args
63
+ in [Range]
64
+ { gte: args.first.first, lte: args.first.last }
65
+ in [String, String]
66
+ { gte: args.first, lte: args.last }
67
+ in [Hash]
68
+ args.first
69
+ else
70
+ raise ArgumentError, "Expected either a range or a upper and lower bounds, got #{args}"
71
+ end
72
+ current_operation << {
73
+ range: { "#{field}": fragment },
74
+ }
75
+ self
76
+ end
77
+
78
+ def current_operation
79
+ operations[last_operation]
80
+ end
81
+
82
+ def current_params(value)
83
+ if value.key?(Constants::ALLOWED_PARAMS)
84
+ operations.merge!(value)
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,16 @@
1
+ module DVLA
2
+ module Kaping
3
+ module Search
4
+ # could pass in a home rolled client
5
+ def search(body)
6
+ client = DVLA::Kaping::AWSClient.new
7
+ con = client.connect
8
+ con.search(
9
+ index: Kaping.yaml[:kaping_index],
10
+ body: body,
11
+ size: Kaping.yaml[:kaping_result_size],
12
+ )
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DVLA
4
+ module Kaping
5
+ VERSION = '1.0.0'
6
+ end
7
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require_relative 'kaping/version'
5
+ require_relative 'kaping/config'
6
+ require_relative 'kaping/aws_client'
7
+ require_relative 'kaping/query'
8
+ require_relative 'kaping/query_term'
9
+ require_relative 'kaping/search'
10
+
11
+ module DVLA
12
+ module Kaping
13
+ extend DVLA::Kaping::Search
14
+
15
+ CONFIG = "#{Gem::Specification.find_by_name('dvla-kaping').gem_dir.freeze}/config".freeze
16
+
17
+ def self.config
18
+ @config ||= DVLA::Kaping::Config.new
19
+ end
20
+
21
+ def self.configure
22
+ yield config
23
+ end
24
+
25
+ def self.logger
26
+ config.logger
27
+ end
28
+
29
+ def self.yaml
30
+ logger.warn { 'Environment not set!'.red } unless config.yaml
31
+ config.yaml || {}
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,6 @@
1
+ module Dvla
2
+ module Kaping
3
+ VERSION: String
4
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ # sonar-project.properties
2
+ sonar.sources=lib
3
+ sonar.tests=spec
4
+ sonar.ruby.coverage.reportPaths=coverage/coverage.json
5
+ sonar.testExecutionReportPaths=out/test-report.xml
metadata ADDED
@@ -0,0 +1,83 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dvla-kaping
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Driver and Vehicle Licensing Agency (DVLA)
8
+ - Kevin Upstill
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2025-04-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: nokogiri
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.16'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 1.16.7
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '1.16'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 1.16.7
33
+ description: Wrapper for the AWS elastic search API to create an idiomatic way to
34
+ build complex search queries
35
+ email:
36
+ - Kevin.Upstill@dvla.gov.uk
37
+ executables: []
38
+ extensions: []
39
+ extra_rdoc_files: []
40
+ files:
41
+ - ".drone.yml"
42
+ - ".rspec"
43
+ - ".rubocop.yml"
44
+ - ".ruby-version"
45
+ - CHANGELOG.md
46
+ - CODE_OF_CONDUCT.md
47
+ - LICENCE
48
+ - README.md
49
+ - Rakefile
50
+ - config/kaping.yml
51
+ - lib/dvla/kaping.rb
52
+ - lib/dvla/kaping/aws_client.rb
53
+ - lib/dvla/kaping/config.rb
54
+ - lib/dvla/kaping/query.rb
55
+ - lib/dvla/kaping/query_term.rb
56
+ - lib/dvla/kaping/search.rb
57
+ - lib/dvla/kaping/version.rb
58
+ - sig/dvla/kaping.rbs
59
+ - sonar-project.properties
60
+ homepage: https://github.com/dvla/kaping
61
+ licenses:
62
+ - MIT
63
+ metadata:
64
+ homepage_uri: https://github.com/dvla/kaping
65
+ source_code_uri: https://github.com/dvla/kaping
66
+ rdoc_options: []
67
+ require_paths:
68
+ - lib
69
+ required_ruby_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: '3'
74
+ required_rubygems_version: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ requirements: []
80
+ rubygems_version: 3.6.2
81
+ specification_version: 4
82
+ summary: Idiomatic way to create DSL openSearch definitions
83
+ test_files: []