calmlens 0.18.3 → 0.20.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 +4 -4
- data/README.md +3 -1
- data/lib/calmlens/assets.rb +142 -42
- data/lib/calmlens/cdn.rb +14 -7
- data/lib/calmlens/error_codes.rb +28 -2
- data/lib/calmlens/events.rb +5 -2
- data/lib/calmlens/folders.rb +9 -6
- data/lib/calmlens/hash_lists.rb +11 -8
- data/lib/calmlens/internal.rb +10 -0
- data/lib/calmlens/lenses.rb +6 -2
- data/lib/calmlens/option_keys.rb +89 -0
- data/lib/calmlens/policies.rb +100 -0
- data/lib/calmlens/resource.rb +32 -1
- data/lib/calmlens/server_client.rb +23 -6
- data/lib/calmlens/session.rb +6 -3
- data/lib/calmlens/testing/classification.rb +3 -0
- data/lib/calmlens/testing/fake_api.rb +206 -20
- data/lib/calmlens/testing/test_image_grid.rb +161 -0
- data/lib/calmlens/testing/test_input_scenario.rb +127 -0
- data/lib/calmlens/testing/test_inputs.rb +157 -0
- data/lib/calmlens/testing.rb +10 -2
- data/lib/calmlens/text.rb +5 -4
- data/lib/calmlens/transform_rules.rb +120 -0
- data/lib/calmlens/transform_schema.rb +236 -0
- data/lib/calmlens/transforms.rb +37 -28
- data/lib/calmlens/upload_body.rb +14 -0
- data/lib/calmlens/upload_mime_types.rb +43 -0
- data/lib/calmlens/upload_tokens.rb +4 -1
- data/lib/calmlens/version.rb +1 -1
- data/lib/calmlens.rb +5 -0
- data/sig/calmlens.rbs +59 -3
- data/sig/error_codes.rbs +14 -1
- data/sig/types.rbs +104 -20
- metadata +10 -2
data/lib/calmlens/hash_lists.rb
CHANGED
|
@@ -5,12 +5,15 @@ module CalmLens
|
|
|
5
5
|
# project: a `flag` list reports matches, a `block` list also blocks close matches. Reach it through
|
|
6
6
|
# {ServerClient#hash_lists}.
|
|
7
7
|
class HashLists < Resource
|
|
8
|
+
# The prefix of this resource's operations in Internal::OPTION_KEYS.
|
|
9
|
+
OPERATIONS = 'hashLists'
|
|
10
|
+
|
|
8
11
|
# Lists one page of the project's hash lists, newest first.
|
|
9
12
|
# @option options [String] :cursor the previous page's `nextCursor`
|
|
10
13
|
# @option options [Integer] :page_size 1 to 100, default 50
|
|
11
14
|
# @return [Hash] a cursor page: items, nextCursor, hasMore, pageSize and ordering.
|
|
12
15
|
def list(options = {})
|
|
13
|
-
call(options) do |api, request|
|
|
16
|
+
call(options, __method__) do |api, request|
|
|
14
17
|
response = request('GET', project('hash-lists'), query: api, request_options: request)
|
|
15
18
|
valid = response.is_a?(Hash) && response['items'].is_a?(Array)
|
|
16
19
|
raise InvalidResponseError.new(SdkErrorCodes::INVALID_RESPONSE, 'Invalid hash list page') unless valid
|
|
@@ -22,7 +25,7 @@ module CalmLens
|
|
|
22
25
|
# @option options [String] :hash_list_id
|
|
23
26
|
# @return [Hash] the hash list
|
|
24
27
|
def retrieve(options)
|
|
25
|
-
call(options) { |api, request| request('GET', list_path(api), request_options: request) }
|
|
28
|
+
call(options, __method__) { |api, request| request('GET', list_path(api), request_options: request) }
|
|
26
29
|
end
|
|
27
30
|
|
|
28
31
|
# @option options [String] :name
|
|
@@ -30,7 +33,7 @@ module CalmLens
|
|
|
30
33
|
# @option options [String, nil] :description
|
|
31
34
|
# @return [Hash] the hash list
|
|
32
35
|
def create(options)
|
|
33
|
-
call(options) { |api, request| request('POST', project('hash-lists'), body: api, request_options: request) }
|
|
36
|
+
call(options, __method__) { |api, request| request('POST', project('hash-lists'), body: api, request_options: request) }
|
|
34
37
|
end
|
|
35
38
|
|
|
36
39
|
# Renames a list or changes what its matches do. The new action applies to later uploads.
|
|
@@ -40,7 +43,7 @@ module CalmLens
|
|
|
40
43
|
# @option options [String] :action `flag` or `block`
|
|
41
44
|
# @return [Hash] the hash list
|
|
42
45
|
def update(options)
|
|
43
|
-
call(options) do |api, request|
|
|
46
|
+
call(options, __method__) do |api, request|
|
|
44
47
|
request('PATCH', list_path(api), body: without(api, 'hashListId'), request_options: request)
|
|
45
48
|
end
|
|
46
49
|
end
|
|
@@ -49,7 +52,7 @@ module CalmLens
|
|
|
49
52
|
# @option options [String] :hash_list_id
|
|
50
53
|
# @return [nil]
|
|
51
54
|
def delete(options)
|
|
52
|
-
call(options) do |api, request|
|
|
55
|
+
call(options, __method__) do |api, request|
|
|
53
56
|
request('DELETE', list_path(api), request_options: request)
|
|
54
57
|
nil
|
|
55
58
|
end
|
|
@@ -61,7 +64,7 @@ module CalmLens
|
|
|
61
64
|
# @option options [Integer] :page_size 1 to 100, default 50
|
|
62
65
|
# @return [Hash] a cursor page: items, nextCursor, hasMore, pageSize and ordering.
|
|
63
66
|
def list_entries(options)
|
|
64
|
-
call(options) do |api, request|
|
|
67
|
+
call(options, __method__) do |api, request|
|
|
65
68
|
request('GET', "#{list_path(api)}/entries", query: without(api, 'hashListId'), request_options: request)
|
|
66
69
|
end
|
|
67
70
|
end
|
|
@@ -74,7 +77,7 @@ module CalmLens
|
|
|
74
77
|
# @option options [String] :label
|
|
75
78
|
# @return [Hash] the entry
|
|
76
79
|
def add_entry(options)
|
|
77
|
-
call(options) do |api, request|
|
|
80
|
+
call(options, __method__) do |api, request|
|
|
78
81
|
request('POST', "#{list_path(api)}/entries", body: without(api, 'hashListId'), request_options: request)
|
|
79
82
|
end
|
|
80
83
|
end
|
|
@@ -82,7 +85,7 @@ module CalmLens
|
|
|
82
85
|
# @option options [String] :entry_id
|
|
83
86
|
# @return [nil]
|
|
84
87
|
def remove_entry(options)
|
|
85
|
-
call(options) do |api, request|
|
|
88
|
+
call(options, __method__) do |api, request|
|
|
86
89
|
request('DELETE', "/hash-list-entries/#{Internal.segment(api['entryId'])}", request_options: request)
|
|
87
90
|
nil
|
|
88
91
|
end
|
data/lib/calmlens/internal.rb
CHANGED
|
@@ -79,6 +79,16 @@ module CalmLens
|
|
|
79
79
|
value.is_a?(Numeric) && value.real? && value.to_f.finite? && value.positive?
|
|
80
80
|
end
|
|
81
81
|
|
|
82
|
+
# Whether the API accepts an upload of this MIME type: an image, audio, video, text, document or
|
|
83
|
+
# website type, not a container such as `application/octet-stream`. It refuses any other type
|
|
84
|
+
# with `upload/invalid-mime-type`.
|
|
85
|
+
def accepted_upload_mime_type?(type)
|
|
86
|
+
mime = type.to_s.split(';', 2).first.to_s.strip.downcase
|
|
87
|
+
return false if UPLOAD_MIME_REJECTED.include?(mime)
|
|
88
|
+
|
|
89
|
+
mime.start_with?(*UPLOAD_MIME_PREFIXES) || mime.end_with?(*UPLOAD_MIME_SUFFIXES) || UPLOAD_MIME_TYPES.include?(mime)
|
|
90
|
+
end
|
|
91
|
+
|
|
82
92
|
# Fetches a required option.
|
|
83
93
|
def required(options, key, code = SdkErrorCodes::INVALID_ARGUMENT)
|
|
84
94
|
raise invalid("#{key} is required", code) unless options.key?(key)
|
data/lib/calmlens/lenses.rb
CHANGED
|
@@ -4,12 +4,16 @@ module CalmLens
|
|
|
4
4
|
# Lens definitions, cached per client. Used through {ServerClient#ensure_lenses} and {ServerClient#ensure_lens}.
|
|
5
5
|
# @api private
|
|
6
6
|
class Lenses < Resource
|
|
7
|
+
# The prefix of this resource's operations in Internal::OPTION_KEYS.
|
|
8
|
+
OPERATIONS = nil
|
|
9
|
+
|
|
10
|
+
# Lens definitions are sent as given, nil fields included.
|
|
7
11
|
def ensure_lenses(options)
|
|
8
|
-
call(options) { |api, request| @session.ensure_lenses(api['lenses'], request) }
|
|
12
|
+
call(options, __method__, keep_nil: true) { |api, request| @session.ensure_lenses(api['lenses'], request) }
|
|
9
13
|
end
|
|
10
14
|
|
|
11
15
|
def ensure_lens(options)
|
|
12
|
-
call(options) { |api, request| @session.ensure_lenses([api], request).first }
|
|
16
|
+
call(options, __method__, keep_nil: true) { |api, request| @session.ensure_lenses([api], request).first }
|
|
13
17
|
end
|
|
14
18
|
end
|
|
15
19
|
private_constant :Lenses
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Generated by packages/sdk-contract/generate.ts from packages/shared/src/PublicApiSchemas.ts.
|
|
4
|
+
# Do not edit by hand: run `bun packages/sdk-contract/generate.ts`.
|
|
5
|
+
module CalmLens
|
|
6
|
+
module Internal
|
|
7
|
+
# The camelCase option keys each method takes, besides `request_options`.
|
|
8
|
+
OPTION_KEYS = {
|
|
9
|
+
'assets.batch' => %w[assetIds operation].freeze,
|
|
10
|
+
'assets.classify' => %w[
|
|
11
|
+
authorId content context description externalId externalUrl folderId isFinal metadata name part
|
|
12
|
+
processingOptions sizeInBytes totalSizeInBytes uploadPresetId url visibility
|
|
13
|
+
].freeze,
|
|
14
|
+
'assets.create' => %w[
|
|
15
|
+
authorId content context description externalId externalUrl folderId isFinal metadata name part
|
|
16
|
+
processingOptions sizeInBytes totalSizeInBytes uploadPresetId url visibility
|
|
17
|
+
].freeze,
|
|
18
|
+
'assets.createSignedUrl' => %w[assetId expiresInSeconds lens resource transform viewerClaims].freeze,
|
|
19
|
+
'assets.decide' => %w[assetId confirmPurge decision note].freeze,
|
|
20
|
+
'assets.delete' => %w[assetId].freeze,
|
|
21
|
+
'assets.deleteContent' => %w[assetId].freeze,
|
|
22
|
+
'assets.each' => %w[cursor filter folderId ordering pageSize].freeze,
|
|
23
|
+
'assets.eachChild' => %w[assetId cursor ordering pageSize].freeze,
|
|
24
|
+
'assets.explain' => %w[assetId rerun].freeze,
|
|
25
|
+
'assets.list' => %w[cursor filter folderId ordering pageSize].freeze,
|
|
26
|
+
'assets.listChildren' => %w[assetId cursor ordering pageSize].freeze,
|
|
27
|
+
'assets.move' => %w[assetId folderId].freeze,
|
|
28
|
+
'assets.retrieve' => %w[assetId].freeze,
|
|
29
|
+
'assets.retry' => %w[assetId].freeze,
|
|
30
|
+
'assets.search' => %w[limit mode query].freeze,
|
|
31
|
+
'assets.update' => %w[assetId description externalId externalUrl metadata name tags visibility].freeze,
|
|
32
|
+
'assets.uploadPart' => %w[assetId part queue].freeze,
|
|
33
|
+
'ensureLens' => %w[active description identifier name transform type version].freeze,
|
|
34
|
+
'ensureLenses' => %w[lenses].freeze,
|
|
35
|
+
'events.each' => %w[cursor pageSize].freeze,
|
|
36
|
+
'events.eachPage' => %w[cursor pageSize].freeze,
|
|
37
|
+
'events.list' => %w[cursor pageSize].freeze,
|
|
38
|
+
'folders.create' => %w[name parentId].freeze,
|
|
39
|
+
'folders.delete' => %w[folderId].freeze,
|
|
40
|
+
'folders.list' => %w[cursor ordering pageSize parentId search].freeze,
|
|
41
|
+
'folders.move' => %w[folderId parentId].freeze,
|
|
42
|
+
'folders.retrieve' => %w[folderId].freeze,
|
|
43
|
+
'folders.update' => %w[folderId name].freeze,
|
|
44
|
+
'hashLists.addEntry' => %w[assetId hash hashListId label quality].freeze,
|
|
45
|
+
'hashLists.create' => %w[action description name].freeze,
|
|
46
|
+
'hashLists.delete' => %w[hashListId].freeze,
|
|
47
|
+
'hashLists.list' => %w[cursor ordering pageSize].freeze,
|
|
48
|
+
'hashLists.listEntries' => %w[cursor hashListId ordering pageSize].freeze,
|
|
49
|
+
'hashLists.removeEntry' => %w[entryId].freeze,
|
|
50
|
+
'hashLists.retrieve' => %w[hashListId].freeze,
|
|
51
|
+
'hashLists.update' => %w[action description hashListId name].freeze,
|
|
52
|
+
'policies.applyPreset' => %w[operation policyId presetVersion revision].freeze,
|
|
53
|
+
'policies.assign' => %w[policyId].freeze,
|
|
54
|
+
'policies.create' => %w[description name presetId settings].freeze,
|
|
55
|
+
'policies.delete' => %w[policyId].freeze,
|
|
56
|
+
'policies.list' => %w[].freeze,
|
|
57
|
+
'policies.retrieve' => %w[policyId].freeze,
|
|
58
|
+
'policies.setAutomaticUpdates' => %w[enabled policyId presetVersion revision].freeze,
|
|
59
|
+
'policies.update' => %w[description name policyId revision settings].freeze,
|
|
60
|
+
'text.classify' => %w[
|
|
61
|
+
authorId context description externalId externalUrl folderId metadata name processingOptions text
|
|
62
|
+
uploadPresetId visibility
|
|
63
|
+
].freeze
|
|
64
|
+
}.freeze
|
|
65
|
+
|
|
66
|
+
# The option keys each method sends as null when given nil; any other option given as nil
|
|
67
|
+
# is left out, as the API refuses null for it.
|
|
68
|
+
NULLABLE_OPTION_KEYS = {
|
|
69
|
+
'assets.classify' => %w[
|
|
70
|
+
authorId context description externalId folderId metadata processingOptions uploadPresetId visibility
|
|
71
|
+
].freeze,
|
|
72
|
+
'assets.create' => %w[
|
|
73
|
+
authorId context description externalId folderId metadata processingOptions uploadPresetId visibility
|
|
74
|
+
].freeze,
|
|
75
|
+
'assets.each' => %w[filter folderId].freeze,
|
|
76
|
+
'assets.list' => %w[filter folderId].freeze,
|
|
77
|
+
'assets.move' => %w[folderId].freeze,
|
|
78
|
+
'assets.update' => %w[description externalId externalUrl metadata tags visibility].freeze,
|
|
79
|
+
'folders.create' => %w[parentId].freeze,
|
|
80
|
+
'folders.move' => %w[parentId].freeze,
|
|
81
|
+
'hashLists.addEntry' => %w[label quality].freeze,
|
|
82
|
+
'hashLists.create' => %w[description].freeze,
|
|
83
|
+
'hashLists.update' => %w[description].freeze,
|
|
84
|
+
'text.classify' => %w[
|
|
85
|
+
authorId context description externalId folderId metadata processingOptions uploadPresetId visibility
|
|
86
|
+
].freeze
|
|
87
|
+
}.freeze
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CalmLens
|
|
4
|
+
# Moderation policies belong to the project's organisation and its projects share them. Every
|
|
5
|
+
# change sends the policy's `revision`, so a policy changed since is refused with
|
|
6
|
+
# `policy/revision-conflict`. Through a project's key you can change only policies no other
|
|
7
|
+
# project uses or owns. Reach it through {ServerClient#policies}.
|
|
8
|
+
class Policies < Resource
|
|
9
|
+
# The prefix of this resource's operations in Internal::OPTION_KEYS.
|
|
10
|
+
OPERATIONS = 'policies'
|
|
11
|
+
|
|
12
|
+
# Lists the organisation's policies, oldest first, and the ID of the one the project uses.
|
|
13
|
+
# @return [Hash] `policies` and `projectPolicyId`
|
|
14
|
+
def list(options = {})
|
|
15
|
+
call(options, __method__) { |_api, request| request('GET', project('policies'), request_options: request) }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# @option options [String] :policy_id
|
|
19
|
+
# @return [Hash] the policy
|
|
20
|
+
def retrieve(options)
|
|
21
|
+
call(options, __method__) { |api, request| request('GET', policy_path(api), request_options: request) }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Creates a policy from `settings` (defining every category) with a `name`, or a copy of the
|
|
25
|
+
# latest release of a built-in preset with `preset_id`. It moderates nothing until it is
|
|
26
|
+
# assigned to a project or named on an upload preset.
|
|
27
|
+
# @option options [String] :name required with `settings`; a preset copy defaults to its name
|
|
28
|
+
# @option options [String] :description
|
|
29
|
+
# @option options [Hash] :settings
|
|
30
|
+
# @option options [String] :preset_id `general-community`, `children-platform`, `education-support`
|
|
31
|
+
# or `wholesome-community`
|
|
32
|
+
# @return [Hash] the policy
|
|
33
|
+
def create(options)
|
|
34
|
+
call(options, __method__) { |api, request| request('POST', project('policies'), body: api, request_options: request) }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Changes the name, description or settings (complete, defining every category); later
|
|
38
|
+
# moderation uses them. Saving a policy that follows automatic updates pins it as a copy.
|
|
39
|
+
# @option options [String] :policy_id
|
|
40
|
+
# @option options [Integer] :revision the policy's current revision
|
|
41
|
+
# @option options [String] :name
|
|
42
|
+
# @option options [String] :description
|
|
43
|
+
# @option options [Hash] :settings
|
|
44
|
+
# @return [Hash] the policy
|
|
45
|
+
def update(options)
|
|
46
|
+
call(options, __method__) do |api, request|
|
|
47
|
+
request('PATCH', policy_path(api), body: without(api, 'policyId'), request_options: request)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Resets a preset copy to its pinned preset version, or updates an unchanged copy to the latest
|
|
52
|
+
# preset release.
|
|
53
|
+
# @option options [String] :policy_id
|
|
54
|
+
# @option options [String] :operation `reset` or `update`
|
|
55
|
+
# @option options [Integer] :revision the policy's current revision
|
|
56
|
+
# @option options [Integer] :preset_version `preset.version` to reset, `preset.latestVersion` to update
|
|
57
|
+
# @return [Hash] the policy
|
|
58
|
+
def apply_preset(options)
|
|
59
|
+
call(options, __method__) do |api, request|
|
|
60
|
+
request('POST', "#{policy_path(api)}/preset", body: without(api, 'policyId'), request_options: request)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Switches automatic updates on or off for the organisation's own preset policy. Switching on
|
|
65
|
+
# replaces the saved copy with the latest preset settings under the same ID; switching off pins
|
|
66
|
+
# the current preset version as a copy with unchanged settings.
|
|
67
|
+
# @option options [String] :policy_id
|
|
68
|
+
# @option options [Boolean] :enabled
|
|
69
|
+
# @option options [Integer] :revision the policy's current revision
|
|
70
|
+
# @option options [Integer] :preset_version the preset's `latestVersion`
|
|
71
|
+
# @return [Hash] the policy
|
|
72
|
+
def set_automatic_updates(options) # rubocop:disable Naming/AccessorMethodName -- the shared SDK surface name
|
|
73
|
+
call(options, __method__) do |api, request|
|
|
74
|
+
request('PUT', "#{policy_path(api)}/automatic-updates", body: without(api, 'policyId'), request_options: request)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Deletes a policy that no project or upload preset uses.
|
|
79
|
+
# @option options [String] :policy_id
|
|
80
|
+
# @return [nil]
|
|
81
|
+
def delete(options)
|
|
82
|
+
call(options, __method__) do |api, request|
|
|
83
|
+
request('DELETE', policy_path(api), request_options: request)
|
|
84
|
+
nil
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Chooses the policy the project moderates with. Later uploads use it, except those through an
|
|
89
|
+
# upload preset that names its own policy.
|
|
90
|
+
# @option options [String] :policy_id
|
|
91
|
+
# @return [Hash] the policy
|
|
92
|
+
def assign(options)
|
|
93
|
+
call(options, __method__) { |api, request| request('PUT', project('policy'), body: api, request_options: request) }
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
private
|
|
97
|
+
|
|
98
|
+
def policy_path(api) = project("policies/#{Internal.segment(api['policyId'])}")
|
|
99
|
+
end
|
|
100
|
+
end
|
data/lib/calmlens/resource.rb
CHANGED
|
@@ -15,9 +15,28 @@ module CalmLens
|
|
|
15
15
|
|
|
16
16
|
private
|
|
17
17
|
|
|
18
|
+
# The option keys of methods that send no request of their own, or take options of their own
|
|
19
|
+
# beside a request's; Internal::OPTION_KEYS, generated from the API, has the rest.
|
|
20
|
+
LOCAL_OPTION_KEYS = {
|
|
21
|
+
'assets.waitForDecision' => %w[assetId timeout].freeze,
|
|
22
|
+
'cdn.getAssetUrl' => %w[assetIdentifier lens resource transform viewerClaims].freeze,
|
|
23
|
+
'cdn.serve' => %w[assetIdentifier init lens resource transform viewerClaims].freeze,
|
|
24
|
+
'uploadTokens.sign' => %w[expiresInSeconds externalId uploadPresetId].freeze
|
|
25
|
+
}.freeze
|
|
26
|
+
private_constant :LOCAL_OPTION_KEYS
|
|
27
|
+
|
|
18
28
|
# Runs a public method: splits options, and raises every argument problem as {InvalidArgumentError}.
|
|
19
|
-
|
|
29
|
+
# `method` names the public method: an option it doesn't take, such as a misspelt one, raises
|
|
30
|
+
# `sdk/invalid-argument` instead of being silently dropped.
|
|
31
|
+
# An option given as nil is left out, as if omitted, unless the API takes null for it
|
|
32
|
+
# (Internal::NULLABLE_OPTION_KEYS), such as `description: nil` to clear a description;
|
|
33
|
+
# `keep_nil: true` sends every option as given, as for a lens definition.
|
|
34
|
+
def call(options, method, keep_nil: false)
|
|
35
|
+
operation = [self.class::OPERATIONS, Internal.camelize(method)].compact.join('.')
|
|
36
|
+
check_option_keys(options, operation)
|
|
20
37
|
api, request = Internal.options(options)
|
|
38
|
+
nullable = Internal::NULLABLE_OPTION_KEYS.fetch(operation, [])
|
|
39
|
+
api = api.reject { |key, value| value.nil? && !nullable.include?(key) } unless keep_nil
|
|
21
40
|
yield api, request
|
|
22
41
|
rescue Error
|
|
23
42
|
raise
|
|
@@ -25,6 +44,18 @@ module CalmLens
|
|
|
25
44
|
raise InvalidArgumentError.new(SdkErrorCodes::INVALID_ARGUMENT, e.message), cause: e
|
|
26
45
|
end
|
|
27
46
|
|
|
47
|
+
def check_option_keys(options, operation)
|
|
48
|
+
return unless options.is_a?(Hash)
|
|
49
|
+
|
|
50
|
+
allowed = LOCAL_OPTION_KEYS.fetch(operation) { Internal::OPTION_KEYS.fetch(operation) }
|
|
51
|
+
unknown = options.keys.reject do |key|
|
|
52
|
+
%w[request_options requestOptions].include?(key.to_s) || allowed.include?(Internal.camelize(key))
|
|
53
|
+
end
|
|
54
|
+
return if unknown.empty?
|
|
55
|
+
|
|
56
|
+
raise Internal.invalid("Unknown option#{'s' if unknown.size > 1} for #{operation}: #{unknown.join(', ')}")
|
|
57
|
+
end
|
|
58
|
+
|
|
28
59
|
def request(...) = @session.request(...)
|
|
29
60
|
def project(path) = @session.project(path)
|
|
30
61
|
|
|
@@ -25,6 +25,11 @@ module CalmLens
|
|
|
25
25
|
RETRY_AFTER_MAX = 60
|
|
26
26
|
# The largest response body read, in bytes (32 MiB).
|
|
27
27
|
DEFAULT_MAX_RESPONSE_BYTES = 32 * 1024 * 1024
|
|
28
|
+
# Content larger than this many bytes is uploaded in parts of this size (8 MiB).
|
|
29
|
+
DEFAULT_PART_SIZE_BYTES = 8 * 1024 * 1024
|
|
30
|
+
# The smallest and largest `part_size_bytes`: every part but the last is at least 5 MiB.
|
|
31
|
+
MIN_PART_SIZE_BYTES = 5 * 1024 * 1024
|
|
32
|
+
MAX_PART_SIZE_BYTES = 64 * 1024 * 1024
|
|
28
33
|
# The most lens definitions {#ensure_lenses} sends in one request.
|
|
29
34
|
ENSURE_LENSES_MAX_PER_REQUEST = 100
|
|
30
35
|
|
|
@@ -34,6 +39,8 @@ module CalmLens
|
|
|
34
39
|
attr_reader :folders
|
|
35
40
|
# @return [HashLists]
|
|
36
41
|
attr_reader :hash_lists
|
|
42
|
+
# @return [Policies]
|
|
43
|
+
attr_reader :policies
|
|
37
44
|
# @return [Text]
|
|
38
45
|
attr_reader :text
|
|
39
46
|
# @return [UploadTokens]
|
|
@@ -62,14 +69,18 @@ module CalmLens
|
|
|
62
69
|
# `Retry-After`. Other writes are never retried. 0 disables retries.
|
|
63
70
|
# @param retry_base_delay [Numeric] seconds before the first retry when the API gives no
|
|
64
71
|
# `Retry-After`; later retries wait up to twice as long each time, at most 8 seconds.
|
|
72
|
+
# @param part_size_bytes [Integer] content larger than this many bytes whose size the client knows
|
|
73
|
+
# and can read again (a binary String, base64, or a File, StringIO or other seekable IO) is
|
|
74
|
+
# uploaded in parts of this size: each part is retried on its own, and calling again with the
|
|
75
|
+
# same `idempotency_key` resumes an interrupted upload. From 5 MiB to 64 MiB; default 8 MiB.
|
|
65
76
|
# @param transport [#call, nil] a custom {Transport}; defaults to {Transport::NetHTTP}.
|
|
66
77
|
# @raise [ConfigurationError] `sdk/invalid-config` for a missing or malformed setting.
|
|
67
78
|
def initialize(api_key:, project_id:, base_url: DEFAULT_BASE_URL, cdn_base_url: DEFAULT_CDN_BASE_URL,
|
|
68
79
|
timeout: DEFAULT_TIMEOUT, open_timeout: DEFAULT_OPEN_TIMEOUT, upload_timeout: nil,
|
|
69
80
|
max_retries: DEFAULT_MAX_RETRIES, retry_base_delay: DEFAULT_RETRY_BASE_DELAY,
|
|
70
|
-
max_response_bytes: DEFAULT_MAX_RESPONSE_BYTES, transport: nil)
|
|
81
|
+
max_response_bytes: DEFAULT_MAX_RESPONSE_BYTES, part_size_bytes: DEFAULT_PART_SIZE_BYTES, transport: nil)
|
|
71
82
|
check_config(api_key, project_id, timeout, open_timeout, max_retries, retry_base_delay, transport)
|
|
72
|
-
check_limits(upload_timeout, max_response_bytes)
|
|
83
|
+
check_limits(upload_timeout, max_response_bytes, part_size_bytes)
|
|
73
84
|
@project_id = project_id
|
|
74
85
|
@base_url = ServerClient.check_url(base_url, 'base_url')
|
|
75
86
|
@cdn_base_url = ServerClient.check_url(cdn_base_url, 'cdn_base_url')
|
|
@@ -77,10 +88,11 @@ module CalmLens
|
|
|
77
88
|
cdn_base_url: @cdn_base_url, timeout: timeout, open_timeout: open_timeout,
|
|
78
89
|
max_retries: max_retries, retry_base_delay: retry_base_delay,
|
|
79
90
|
max_response_bytes: max_response_bytes, upload_timeout: upload_timeout,
|
|
80
|
-
transport: transport || Transport::NetHTTP.new)
|
|
91
|
+
part_size_bytes: part_size_bytes, transport: transport || Transport::NetHTTP.new)
|
|
81
92
|
@assets = Assets.new(@session)
|
|
82
93
|
@folders = Folders.new(@session)
|
|
83
94
|
@hash_lists = HashLists.new(@session)
|
|
95
|
+
@policies = Policies.new(@session)
|
|
84
96
|
@text = Text.new(@session, @assets)
|
|
85
97
|
@upload_tokens = UploadTokens.new(@session, api_key)
|
|
86
98
|
@events = Events.new(@session)
|
|
@@ -107,11 +119,11 @@ module CalmLens
|
|
|
107
119
|
nil
|
|
108
120
|
end
|
|
109
121
|
|
|
110
|
-
# Creates or updates named lenses (saved
|
|
122
|
+
# Creates or updates named lenses (saved transforms) and returns them. Definitions
|
|
111
123
|
# this client already saved are not sent again. Supplying the same definition and version is
|
|
112
124
|
# idempotent; changing a lens needs a higher `version`. More than 100 new definitions are sent in
|
|
113
125
|
# consecutive requests, in order.
|
|
114
|
-
# @option options [Array<Hash>] :lenses `[{identifier:, name:, type:, version:,
|
|
126
|
+
# @option options [Array<Hash>] :lenses `[{identifier:, name:, type:, version:, transform:, ...}]`
|
|
115
127
|
# @return [Array<Hash>] the saved lenses, in the given order.
|
|
116
128
|
def ensure_lenses(options) = @lenses.ensure_lenses(options)
|
|
117
129
|
|
|
@@ -135,10 +147,15 @@ module CalmLens
|
|
|
135
147
|
|
|
136
148
|
private
|
|
137
149
|
|
|
138
|
-
def check_limits(upload_timeout, max_response_bytes)
|
|
150
|
+
def check_limits(upload_timeout, max_response_bytes, part_size_bytes)
|
|
139
151
|
unless upload_timeout.nil? || Internal.positive?(upload_timeout)
|
|
140
152
|
raise ConfigurationError.new(SdkErrorCodes::INVALID_CONFIG, 'upload_timeout must be nil or a positive number of seconds')
|
|
141
153
|
end
|
|
154
|
+
unless part_size_bytes.is_a?(Integer) && part_size_bytes.between?(MIN_PART_SIZE_BYTES, MAX_PART_SIZE_BYTES)
|
|
155
|
+
raise ConfigurationError.new(SdkErrorCodes::INVALID_CONFIG,
|
|
156
|
+
"part_size_bytes must be an Integer from #{MIN_PART_SIZE_BYTES} (5 MiB) to " \
|
|
157
|
+
"#{MAX_PART_SIZE_BYTES} (64 MiB)")
|
|
158
|
+
end
|
|
142
159
|
return if max_response_bytes.is_a?(Integer) && max_response_bytes.positive?
|
|
143
160
|
|
|
144
161
|
raise ConfigurationError.new(SdkErrorCodes::INVALID_CONFIG, 'max_response_bytes must be a positive Integer')
|
data/lib/calmlens/session.rb
CHANGED
|
@@ -25,13 +25,16 @@ module CalmLens
|
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
attr_reader :project_id, :base_url, :cdn_base_url
|
|
28
|
+
# Content larger than this many bytes that can be read again is uploaded in parts of this size.
|
|
29
|
+
attr_reader :part_size_bytes
|
|
28
30
|
# Leaves the upload size checks the API also makes (part and image sizes) to the API, so an
|
|
29
31
|
# offline stand-in answers them like the API does. {MockServerClient} sets it.
|
|
30
32
|
attr_accessor :defer_upload_checks
|
|
31
33
|
|
|
32
34
|
def initialize(api_key:, project_id:, base_url:, cdn_base_url:, timeout:, open_timeout:, max_retries:,
|
|
33
|
-
retry_base_delay:, max_response_bytes:, upload_timeout:, transport:)
|
|
35
|
+
retry_base_delay:, max_response_bytes:, upload_timeout:, part_size_bytes:, transport:)
|
|
34
36
|
@api_key = api_key
|
|
37
|
+
@part_size_bytes = part_size_bytes
|
|
35
38
|
@project_id = project_id
|
|
36
39
|
@base_url = base_url
|
|
37
40
|
@cdn_base_url = cdn_base_url
|
|
@@ -161,10 +164,10 @@ module CalmLens
|
|
|
161
164
|
private
|
|
162
165
|
|
|
163
166
|
# The fields that identify a lens definition, as canonical JSON: an unset description or
|
|
164
|
-
#
|
|
167
|
+
# transform is the same definition as an explicit null.
|
|
165
168
|
def lens_key(lens)
|
|
166
169
|
fields = { 'identifier' => lens['identifier'], 'name' => lens['name'], 'description' => lens['description'],
|
|
167
|
-
'type' => lens['type'], '
|
|
170
|
+
'type' => lens['type'], 'transform' => lens['transform'] }
|
|
168
171
|
%w[active version].each { |name| fields[name] = lens[name] if lens.key?(name) }
|
|
169
172
|
JSON.generate(canonical(fields))
|
|
170
173
|
end
|
|
@@ -17,5 +17,8 @@ module CalmLens
|
|
|
17
17
|
"personal-information" => ["personal-information-contact", "personal-information-email", "personal-information-phone", "personal-information-government-id", "personal-information-financial", "personal-information-location", "personal-information-identifiers", "personal-information-credentials", "personal-information-request", "personal-information-self-disclosure", "personal-information-third-party", "personal-information-historical", "personal-information-documentary", "personal-information-educational", "personal-information-fictional", "personal-information-support-prevention"].freeze,
|
|
18
18
|
"profanity" => ["profanity-mild", "profanity-strong", "profanity-slurs", "profanity-obfuscated", "profanity-historical", "profanity-documentary", "profanity-educational", "profanity-fictional", "profanity-support-prevention"].freeze,
|
|
19
19
|
}.freeze
|
|
20
|
+
|
|
21
|
+
# Qualifiers the input type sets, which a test input scenario cannot list.
|
|
22
|
+
INPUT_QUALIFIERS = ["sexual-text", "violence-text"].freeze
|
|
20
23
|
end
|
|
21
24
|
end
|