adobe_pdfservices_ruby 0.1.1

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.
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PdfServices
4
+ module Base
5
+ class Operation
6
+ BASE_ENDPOINT = 'https://pdf-services-ue1.adobe.io/operation/'
7
+ ASSETS_ENDPOINT = 'https://pdf-services-ue1.adobe.io/assets/'
8
+ STATUS = {
9
+ in_progress: 'in progress',
10
+ done: 'done',
11
+ failed: 'failed'
12
+ }.freeze
13
+
14
+ def initialize(api)
15
+ @api = api
16
+ end
17
+
18
+ def upload_asset(asset)
19
+ asset = File.open(asset, 'rb') if asset.is_a?(String)
20
+ Asset.new(@api).upload(asset)
21
+ end
22
+
23
+ def poll_document_result(url, original_asset, &block)
24
+ response = @api.get(url)
25
+ json_response = JSON.parse(response.body.to_s)
26
+ handle_polling_result(url, json_response, original_asset, &block)
27
+ end
28
+
29
+ private
30
+
31
+ def handle_polling_result(url, json_response, original_asset, &block)
32
+ case json_response['status']
33
+ when STATUS[:in_progress]
34
+ handle_in_progress(url, json_response, original_asset, &block)
35
+ when STATUS[:done]
36
+ handle_done(json_response, original_asset, &block)
37
+ when STATUS[:failed]
38
+ handle_failed(json_response, original_asset, &block)
39
+ else
40
+ handle_unexpected_status(json_response, original_asset, &block)
41
+ end
42
+ end
43
+
44
+ def handle_in_progress(url, json_response, original_asset, &block)
45
+ block.call(json_response['status'], nil) if block_given?
46
+ sleep(1) # Consider a more sophisticated retry strategy
47
+ poll_document_result(url, original_asset, &block)
48
+ end
49
+
50
+ def handle_done(json_response, original_asset, &block)
51
+ return block.call(json_response['status'], handle_polling_done(json_response, original_asset)) if block_given?
52
+
53
+ handle_polling_done(json_response, original_asset)
54
+ end
55
+
56
+ def handle_failed(json_response, original_asset, &block)
57
+ block.call(json_response['status'], nil) if block_given?
58
+ handle_polling_failed(json_response, original_asset)
59
+ end
60
+
61
+ def handle_unexpected_status(json_response, original_asset, &block)
62
+ block.call(json_response['status'], nil) if block_given?
63
+ handle_polling_unexpected_status(json_response, original_asset)
64
+ end
65
+
66
+ def handle_polling_done(_json_response, original_asset)
67
+ original_asset.delete
68
+ end
69
+
70
+ def handle_polling_failed(json_response, original_asset)
71
+ original_asset.delete
72
+ raise PollingError, "Document extraction failed: #{json_response['error']}"
73
+ end
74
+
75
+ def handle_polling_unexpected_status(json_response, original_asset)
76
+ original_asset.delete
77
+ raise PollingError, "Unexpected status: #{json_response['status']}"
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PdfServices
4
+ module DocumentGeneration
5
+ class External < Operation
6
+ EXTERNAL_OPTIONS = %i[input output params download_from_external].freeze
7
+ INPUT_KEYS = %i[uri storage].freeze
8
+ OUTPUT_KEYS = %i[uri storage].freeze
9
+ PARAMS_KEYS = %i[output_format json_data_for_merge fragments].freeze
10
+ STORAGE_OPTIONS = %i[S3 SHAREPOINT DROPBOX BLOB].freeze
11
+
12
+ def initialize(api)
13
+ super
14
+ @download_from_external = false
15
+ @download_uri = nil
16
+ end
17
+
18
+ def execute(template_path, options = {})
19
+ validate_options(options)
20
+ asset = upload_asset(template_path)
21
+
22
+ @download_from_external = options[:download_from_external] || false
23
+
24
+ @download_uri = options[:output][:uri] if @download_from_external
25
+
26
+ response = @api.post(OPERATION_ENDPOINT,
27
+ body: request_body(asset.id, options),
28
+ headers: request_headers)
29
+ handle_response(response, asset.id)
30
+ end
31
+
32
+ private
33
+
34
+ def request_body(asset_id, options)
35
+ {
36
+ assetID: asset_id,
37
+ input: camelize_keys(options[:input]),
38
+ output: camelize_keys(options[:output]),
39
+ params: camelize_keys(options[:params])
40
+ }
41
+ end
42
+
43
+ def handle_polling_done(response, original_asset)
44
+ file = @api.get(@download_uri).body if @download_from_external
45
+ super
46
+ file || true
47
+ end
48
+
49
+ def validate_options(options)
50
+ raise ArgumentError, 'Options must be a hash' unless options.is_a?(Hash)
51
+
52
+ validate_required_keys(options)
53
+ validate_input_options(options[:input])
54
+ validate_output_options(options[:output])
55
+ validate_params_options(options[:params])
56
+ end
57
+
58
+ def validate_required_keys(options)
59
+ required_keys = EXTERNAL_OPTIONS - %i[download_from_external]
60
+ required_keys.each do |key|
61
+ raise ArgumentError, "Missing required key: #{key}" unless options.key?(key)
62
+ end
63
+ end
64
+
65
+ def validate_input_options(input_options)
66
+ raise ArgumentError, 'Input options must be a hash' unless input_options.is_a?(Hash)
67
+
68
+ required_input_keys = INPUT_KEYS
69
+ required_input_keys.each do |key|
70
+ raise ArgumentError, "Missing required input key: #{key}" unless input_options.key?(key)
71
+ end
72
+ validate_storage_options(input_options[:storage])
73
+ end
74
+
75
+ def validate_output_options(output_options)
76
+ raise ArgumentError, 'Output options must be a hash' unless output_options.is_a?(Hash)
77
+
78
+ required_output_keys = OUTPUT_KEYS - %i[uri]
79
+ required_output_keys.each do |key|
80
+ raise ArgumentError, "Missing required output key: #{key}" unless output_options.key?(key)
81
+ end
82
+ validate_storage_options(output_options[:storage])
83
+ end
84
+
85
+ def validate_storage_option(storage_option)
86
+ raise ArgumentError, "Invalid storage option: #{storage_option}" unless STORAGE_OPTIONS.include?(storage_option)
87
+ end
88
+
89
+ def validate_params_options(params_options)
90
+ raise ArgumentError, 'Params options must be a hash' unless params_options.is_a?(Hash)
91
+
92
+ required_params_keys = PARAMS_KEYS - %i[output_format fragments]
93
+ required_params_keys.each do |key|
94
+ raise ArgumentError, "Missing required params key: #{key}" unless params_options.key?(key)
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PdfServices
4
+ module DocumentGeneration
5
+ class Internal < Operation
6
+ INTERNAL_OPTIONS = %i[output_format json_data_for_merge fragments notifiers].freeze
7
+
8
+ def execute(template_path, options = {})
9
+ validate_options(options)
10
+ asset = upload_asset(template_path)
11
+
12
+ response = @api.post(OPERATION_ENDPOINT,
13
+ body: request_body(asset.id, options),
14
+ headers: request_headers)
15
+
16
+ handle_response(response, asset.id)
17
+ end
18
+
19
+ private
20
+
21
+ def request_body(asset_id, options)
22
+ body = {
23
+ assetID: asset_id,
24
+ outputFormat: options.fetch(:output_format, 'pdf'),
25
+ jsonDataForMerge: options.fetch(:json_data_for_merge, {})
26
+ }
27
+ body[:fragments] = options[:fragments] if options[:fragments]
28
+ body[:notifiers] = options[:notifiers] if options[:notifiers]
29
+ body
30
+ end
31
+
32
+ def validate_options(options)
33
+ raise ArgumentError, 'Invalid options' unless options.is_a?(Hash)
34
+
35
+ validate_output_format(options[:output_format])
36
+ validate_fragments(options[:fragments])
37
+ validate_notifiers(options[:notifiers])
38
+ validate_keys(options)
39
+ validate_json_data_for_merge(options[:json_data_for_merge])
40
+ end
41
+
42
+ def validate_output_format(output_format)
43
+ raise ArgumentError, "Invalid output format: #{output_format}" unless %w[pdf docx].include?(output_format)
44
+ end
45
+
46
+ def validate_fragments(fragments)
47
+ raise ArgumentError, 'Invalid fragments, must be a hash' unless fragments.is_a?(Hash) || fragments.nil?
48
+ end
49
+
50
+ def validate_notifiers(notifiers)
51
+ return if notifiers.is_a?(Array) || notifiers.nil?
52
+
53
+ raise ArgumentError,
54
+ 'Invalid notifiers, must be an array of hashes'
55
+ end
56
+
57
+ def validate_json_data_for_merge(json_data_for_merge)
58
+ raise ArgumentError, 'Invalid json_data_for_merge, must be a hash' unless json_data_for_merge.is_a?(Hash)
59
+ end
60
+
61
+ def validate_keys(options)
62
+ valid_keys = INTERNAL_OPTIONS
63
+ invalid_keys = options.keys - valid_keys
64
+ raise ArgumentError, "Invalid options: #{invalid_keys}" unless invalid_keys.empty?
65
+ end
66
+
67
+ def handle_polling_done(json_response, original_asset)
68
+ asset_id = json_response['asset']['assetID']
69
+ file = Asset.new(@api).download(asset_id).body
70
+ super
71
+ file
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PdfServices
4
+ module DocumentGeneration
5
+ class Operation < InternalExternalOperation::Operation
6
+ OPERATION_ENDPOINT = "#{BASE_ENDPOINT}documentgeneration".freeze
7
+
8
+ def request_headers
9
+ { 'Content-Type' => 'application/json' }
10
+ end
11
+
12
+ def handle_response(response, asset_id)
13
+ unless response.status == 201
14
+ raise "Unexpected response status from document merge endpoint: #{response.status}, asset_id: #{asset_id}"
15
+ end
16
+
17
+ document_url = response.headers['location']
18
+ poll_document_result document_url, asset_id
19
+ end
20
+
21
+ def internal_class
22
+ Internal
23
+ end
24
+
25
+ def external_class
26
+ External
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PdfServices
4
+ module ExtractPdf
5
+ class Operation < Base::Operation
6
+ OPERATION_ENDPOINT = "#{BASE_ENDPOINT}extractpdf".freeze
7
+ VALID_EXTRACT_ELEMENTS = %w[tables text].freeze
8
+ TABLE_OUTPUT_FORMATS = %w[csv xlsx].freeze
9
+ RENDITIONS_EXTRACTS = %w[tables figures].freeze
10
+
11
+ def execute(source_pdf_path = nil, options = {})
12
+ validate_options(options)
13
+ @download_zip = options.delete(:download_zip) || false
14
+ asset = upload_asset(source_pdf_path)
15
+
16
+ response = @api.post(OPERATION_ENDPOINT,
17
+ body: extract_pdf_request_body(asset.id, options),
18
+ headers: extract_pdf_request_headers)
19
+ handle_extract_pdf_response(response, asset)
20
+ end
21
+
22
+ private
23
+
24
+ def extract_pdf_request_body(asset_id, options)
25
+ {
26
+ assetID: asset_id,
27
+ includeStyling: options.fetch(:include_styling, false),
28
+ getCharBounds: options.fetch(:get_char_bounds, false),
29
+ renditionsToExtract: options.fetch(:renditions_to_extract, RENDITIONS_EXTRACTS),
30
+ tableOutputFormat: options.fetch(:table_output_format, TABLE_OUTPUT_FORMATS.first),
31
+ elementsToExtract: options.fetch(:extract_elements, VALID_EXTRACT_ELEMENTS)
32
+ }
33
+ end
34
+
35
+ def extract_pdf_request_headers
36
+ { 'Content-Type' => 'application/json' }
37
+ end
38
+
39
+ def handle_extract_pdf_response(response, asset)
40
+ raise OperationError, "Extract PDF operation failed: #{response.body}" unless response.status == 201
41
+
42
+ polling_url = response.headers['location']
43
+ poll_document_result polling_url, asset
44
+ end
45
+
46
+ def handle_polling_done(json_response, original_asset)
47
+ file_key = @download_zip ? 'resource' : 'content'
48
+ asset_id = json_response[file_key]['assetID']
49
+ file = Asset.new(@api).download(asset_id).body
50
+ super
51
+ file
52
+ end
53
+
54
+ def validate_options(options)
55
+ validate_renditions_to_extract(options[:renditions_to_extract])
56
+ validate_table_output_format(options[:table_output_format])
57
+ validate_extract_elements(options[:extract_elements])
58
+ end
59
+
60
+ def validate_renditions_to_extract(renditions_to_extract)
61
+ renditions_to_extract ||= RENDITIONS_EXTRACTS
62
+ return if (renditions_to_extract - RENDITIONS_EXTRACTS).empty?
63
+
64
+ raise ArgumentError, "Invalid renditions_to_extract: #{renditions_to_extract}"
65
+ end
66
+
67
+ def validate_table_output_format(table_output_format)
68
+ table_output_format ||= TABLE_OUTPUT_FORMATS.first
69
+ return if TABLE_OUTPUT_FORMATS.include?(table_output_format)
70
+
71
+ raise ArgumentError, "Invalid table_output_format: #{table_output_format}"
72
+ end
73
+
74
+ def validate_extract_elements(extract_elements)
75
+ extract_elements ||= VALID_EXTRACT_ELEMENTS
76
+ invalid_elements = extract_elements - VALID_EXTRACT_ELEMENTS
77
+ return if invalid_elements.empty?
78
+
79
+ raise ArgumentError, "Invalid extract_elements: #{invalid_elements}"
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PdfServices
4
+ module HtmlToPdf
5
+ class External < Operation
6
+ EXTERNAL_OPTIONS = %i[input output params download_from_external].freeze
7
+ INPUT_KEYS = %i[uri storage].freeze
8
+ OUTPUT_KEYS = %i[uri storage].freeze
9
+ PARAMS_KEYS = %i[include_header_footer json page_layout].freeze
10
+ STORAGE_OPTIONS = %i[S3 SHAREPOINT DROPBOX BLOB].freeze
11
+
12
+ def initialize(api)
13
+ super
14
+ @download_from_external = false
15
+ @download_uri = nil
16
+ end
17
+
18
+ def execute(url, options = {})
19
+ @download_from_external = options[:download_from_external] || false
20
+ options[:input][:uri] = options[:input][:uri] || url
21
+ validate_options(options)
22
+
23
+ @download_uri = options[:output][:uri] if @download_from_external
24
+
25
+ response = @api.post(OPERATION_ENDPOINT,
26
+ body: request_body(asset.id, options),
27
+ headers: request_headers)
28
+ handle_response(response, asset.id)
29
+ end
30
+
31
+ private
32
+
33
+ def request_body(asset_id, options)
34
+ {
35
+ assetID: asset_id,
36
+ input: camelize_keys(options[:input]),
37
+ output: camelize_keys(options[:output]),
38
+ params: camelize_keys(options[:params])
39
+ }
40
+ end
41
+
42
+ def handle_polling_done(_json_response, original_asset)
43
+ file = @api.get(@download_uri).body if @download_from_external
44
+ super
45
+ file || true
46
+ end
47
+
48
+ def validate_options(options)
49
+ raise ArgumentError, 'Options must be a hash' unless options.is_a?(Hash)
50
+
51
+ validate_required_keys(options)
52
+ validate_input_options(options[:input])
53
+ validate_output_options(options[:output])
54
+ end
55
+
56
+ def validate_required_keys(options)
57
+ required_keys = EXTERNAL_OPTIONS - %i[download_from_external]
58
+ required_keys.each do |key|
59
+ raise ArgumentError, "Missing required key: #{key}" unless options.key?(key)
60
+ end
61
+ end
62
+
63
+ def validate_input_options(input_options)
64
+ raise ArgumentError, 'Input options must be a hash' unless input_options.is_a?(Hash)
65
+
66
+ required_input_keys = INPUT_KEYS
67
+ required_input_keys.each do |key|
68
+ raise ArgumentError, "Missing required input key: #{key}" unless input_options.key?(key)
69
+ end
70
+ validate_storage_options(input_options[:storage])
71
+ end
72
+
73
+ def validate_output_options(output_options)
74
+ raise ArgumentError, 'Output options must be a hash' unless output_options.is_a?(Hash)
75
+
76
+ unless output_options.key?(:uri) && @download_from_external
77
+ raise ArgumentError,
78
+ 'Output options must contain uri when downloading from external storage'
79
+ end
80
+
81
+ required_output_keys = OUTPUT_KEYS - %i[uri]
82
+ required_output_keys.each do |key|
83
+ raise ArgumentError, "Missing required output key: #{key}" unless output_options.key?(key)
84
+ end
85
+ validate_storage_options(output_options[:storage])
86
+ end
87
+
88
+ def validate_storage_option(storage_option)
89
+ return if STORAGE_OPTIONS.include?(storage_option)
90
+
91
+ raise ArgumentError,
92
+ "Invalid storage option: #{storage_option}"
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PdfServices
4
+ module HtmlToPdf
5
+ class Internal < Operation
6
+ INTERNAL_OPTIONS = %i[input_url json include_header_footer page_layout notifiers].freeze
7
+ PAGE_LAYOUT_OPTIONS = %i[page_width page_height].freeze
8
+
9
+ def execute(html_file_path, options = {})
10
+ validate_options(options)
11
+ asset = upload_asset(html_file_path)
12
+
13
+ response = @api.post(OPERATION_ENDPOINT,
14
+ body: request_body(asset.id, options),
15
+ headers: request_headers)
16
+
17
+ handle_response(response, asset.id)
18
+ end
19
+
20
+ private
21
+
22
+ def handle_polling_done(json_response, _original_asset_id)
23
+ asset_id = json_response['asset']['assetID']
24
+ Asset.new(@api).download(asset_id).body
25
+ end
26
+
27
+ def request_body(asset_id, options)
28
+ body = {
29
+ assetID: asset_id,
30
+ inputUrl: options.fetch(:input_url, ''),
31
+ pageLayout: camelize_keys(options.fetch(:page_layout, {})),
32
+ json: transform_json(options.fetch(:json, ''))
33
+ }
34
+ body[:notifiers] = options[:notifiers] if options[:notifiers]
35
+ body
36
+ end
37
+
38
+ def transform_json(json)
39
+ json.is_a?(String) ? json : json.to_json
40
+ end
41
+
42
+ def validate_options(options)
43
+ raise ArgumentError, 'Invalid options' unless options.is_a?(Hash)
44
+
45
+ options.each_key do |key|
46
+ raise ArgumentError, "Invalid option: #{key}" unless INTERNAL_OPTIONS.include?(key)
47
+ end
48
+
49
+ validate_required_keys(options)
50
+ validate_page_layout_options(options[:page_layout]) if options[:page_layout]
51
+ end
52
+
53
+ def validate_required_keys(options)
54
+ required_keys = INTERNAL_OPTIONS - %i[page_layout notifiers]
55
+ required_keys.each do |key|
56
+ raise ArgumentError, "Missing required option: #{key}" unless options.key?(key)
57
+ end
58
+ end
59
+
60
+ def validate_page_layout_options(options)
61
+ raise ArgumentError, 'Invalid page layout options' unless options.is_a?(Hash)
62
+
63
+ options.each_key do |key|
64
+ raise ArgumentError, "Invalid page layout option: #{key}" unless PAGE_LAYOUT_OPTIONS.include?(key)
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PdfServices
4
+ module HtmlToPdf
5
+ class Operation < InternalExternalOperation::Operation
6
+ OPERATION_ENDPOINT = "#{BASE_ENDPOINT}htmltopdf".freeze
7
+
8
+ def request_headers
9
+ { 'Content-Type' => 'application/json' }
10
+ end
11
+
12
+ def handle_response(response, asset_id)
13
+ unless response.status == 201
14
+ raise "Unexpected response status from document merge endpoint: #{response.status}, asset_id: #{asset_id}"
15
+ end
16
+
17
+ document_url = response.headers['location']
18
+ poll_document_result document_url, asset_id
19
+ end
20
+
21
+ def internal_class
22
+ Internal
23
+ end
24
+
25
+ def external_class
26
+ External
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,40 @@
1
+ module PdfServices
2
+ module InternalExternalOperation
3
+ class Operation < Base::Operation
4
+ def execute(source_file_path, options = {})
5
+ operation_class = switch_on_type(options)
6
+ operation_class.new(@api).execute(source_file_path, options)
7
+ end
8
+
9
+ private
10
+
11
+ def switch_on_type(options)
12
+ type = options[:type] || :internal
13
+ case type
14
+ when :internal
15
+ internal_class
16
+ when :external
17
+ external_class
18
+ end
19
+ end
20
+
21
+ def internal_class
22
+ raise NotImplementedError, 'Subclasses must implement this method'
23
+ end
24
+
25
+ def external_class
26
+ raise NotImplementedError, 'Subclasses must implement this method'
27
+ end
28
+
29
+ def camelize_keys(hash)
30
+ hash.transform_keys { |key| camelize(key.to_s) }
31
+ end
32
+
33
+ def camelize(str)
34
+ caps = str.split('_').map(&:capitalize)
35
+ caps[0].downcase!
36
+ caps.join
37
+ end
38
+ end
39
+ end
40
+ end