entrata 1.1.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: 2316690e03ac91b2580dbaab63c56ac93afc70eaef268c787d206aba024a484f
4
+ data.tar.gz: 574ed8030e63871b2cb1a59afd3e5ea25a57a995b790ba5a2a6c606aa3965db3
5
+ SHA512:
6
+ metadata.gz: 678ea4ce2d8dbf797263d707df80c21351f26085c6e690d5024a23a346c52a272695f049e23c3a74cf536ff96257156878c9ba019ee050d62046f42ace087fe2
7
+ data.tar.gz: e92102c9060201f39c692dbac217f27b77b60d131ac5ff12d33b639453225d728fd02c6b1548bad0423b12a04be91820e495d5e4736bd76498ec1ddf4b4d7c64
data/.ci ADDED
@@ -0,0 +1,144 @@
1
+ #!/usr/bin/env groovy
2
+
3
+ // https://github.com/apartmentlist/ci-shared-library
4
+ @Library('ci-shared-library')_
5
+
6
+ // Log Rotation
7
+ properties([
8
+ buildDiscarder(
9
+ logRotator(
10
+ artifactDaysToKeepStr: '',
11
+ artifactNumToKeepStr: '',
12
+ daysToKeepStr: '30',
13
+ numToKeepStr: '100'
14
+ )
15
+ )
16
+ ]) //properties
17
+
18
+ // Generate unique slave labels
19
+ def k8s_label = "${UUID.randomUUID().toString()}"
20
+
21
+ pipeline {
22
+ environment {
23
+ APP_NAME = 'entrata'
24
+ APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE = 'true'
25
+ CI = 'true'
26
+ CLOUDSDK_CORE_DISABLE_PROMPTS = '1'
27
+ GIT_COMMIT_SHORT = sh(script: "git rev-parse --short ${GIT_COMMIT}", returnStdout: true).trim()
28
+ GIT_MESSAGE = sh(script: "git log --format=%B -n 1 ${GIT_COMMIT}", returnStdout: true).trim()
29
+ GIT_USER = sh(script: "git log -1 --pretty=format:'%ae'", returnStdout: true).trim()
30
+ GITHUB_URL = "https://github.com"
31
+ LANG = "en_US.UTF-8"
32
+ LANGUAGE = "en_US:en"
33
+ LC_ALL = "en_US.UTF-8"
34
+ PRODUCTION_DEPLOY="false"
35
+ SLACK_SUCCESS_CHANNEL = "#staging-releases"
36
+ SLACK_FAILURE_CHANNEL = "#supply_log_info"
37
+ } // environment
38
+
39
+ agent {
40
+ kubernetes {
41
+ label k8s_label
42
+ defaultContainer 'jnlp'
43
+ yaml """
44
+ ---
45
+ apiVersion: v1
46
+ kind: Pod
47
+ metadata:
48
+ name: test
49
+ spec:
50
+ restartPolicy: Never
51
+ containers:
52
+ - name: ruby
53
+ image: gcr.io/alist-development/ruby:2.7.6
54
+ imagePullPolicy: Always
55
+ resources:
56
+ requests:
57
+ memory: "1024Mi"
58
+ cpu: "1"
59
+ requests:
60
+ memory: "1024Mi"
61
+ cpu: "1"
62
+ command:
63
+ - "tail"
64
+ - "-f"
65
+ - "/dev/null"
66
+ """
67
+ } // kubernetes
68
+ } // agent
69
+
70
+ options {
71
+ timestamps()
72
+ timeout(time: 10, unit: 'MINUTES')
73
+ ansiColor('xterm')
74
+ } // options
75
+
76
+ stages {
77
+ stage('Preparation') {
78
+ parallel {
79
+ stage('Slack') {
80
+ steps {
81
+ slackPreparation()
82
+ } // steps
83
+ } // stage - Slack
84
+ stage('Build Description') {
85
+ steps {
86
+ buildDescription()
87
+ }
88
+ }
89
+ stage('Bundle') {
90
+ steps {
91
+ container('ruby') {
92
+ sh 'gem install bundler:2.2.6 --no-post-install-message --no-document'
93
+ sh 'bundle install -j 12'
94
+ } // container
95
+ } // steps
96
+ } // stage - Bundle
97
+ } //parallel
98
+ } //stage - Preparation
99
+
100
+ stage('Testing') {
101
+ when {
102
+ allOf {
103
+ changeRequest target: 'main'
104
+ }
105
+ }
106
+
107
+ parallel {
108
+ stage('Run RSpec') {
109
+ steps {
110
+ container('ruby') {
111
+ sh label: 'RSpec', script: 'bundle exec rake'
112
+ } // container
113
+ } // steps
114
+ } // stage
115
+ } //parallel
116
+ } //stage
117
+
118
+ stage("Publish") {
119
+ when {
120
+ allOf {
121
+ branch "main"
122
+ not { changeRequest() }
123
+ }
124
+ } // when
125
+ parallel {
126
+ stage("to Github packages") {
127
+ steps {
128
+ publishRubyGemToGHP("ruby")
129
+ }
130
+ } // stage - to Github packages
131
+ } // parallel
132
+ } // stage - Publish
133
+ } // stages
134
+
135
+ post {
136
+ success {
137
+ success('alist-production')
138
+ } // success
139
+ aborted {
140
+ aborted('alist-production')
141
+ } // aborted
142
+ } // post
143
+
144
+ } // pipeline
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ /Gemfile.lock
2
+ entrata-*.gem
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require 'spec_helper'
data/CODEOWNERS ADDED
@@ -0,0 +1 @@
1
+ * @apartmentlist/integrations
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in entrata.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,110 @@
1
+ [![CircleCI](https://circleci.com/gh/apartmentlist/entrata.svg?style=svg&circle-token=838474bf01ba4796ffc9f2b59038523b3b6a2b90)](https://circleci.com/gh/apartmentlist/entrata)
2
+
3
+ # Entrata
4
+
5
+ Ruby client for Entrata API
6
+
7
+ ## Versions
8
+ * 0.1.0 Initial Release
9
+ * 0.1.1 Extend request timeout to 10 minutes for all requests
10
+ * 0.1.2 Request GetIlsPropertiesData via `curl` to avoid errors on large responses
11
+ * 0.2.0 Add buildingName to fixture file for getIlsPropertiesData success response
12
+ * 0.2.1 Make paths in StaticFileFetcher relative
13
+ * 0.2.2 Add buildingName to unit C606 in fixture file for successful getIlsPropertiesData
14
+ * 0.3.0 Raise a Request::Error when the response is not valid JSON and update Travis Ruby version
15
+ * 0.3.2 Expose lead request body for troubleshooting when sending leads
16
+ * 1.0.0 Allow us to pass renter min and max price range in
17
+ * 1.0.4 Make entrata gem compatible with Ruby 3 syntax updates
18
+ * 1.0.5 No functional changes. Updating build pipeline to publish to GitHub Packages.
19
+ * 1.1.0 Adding services to manage lea lite inactive and reactive guestcard requests.
20
+
21
+ ## Installation
22
+
23
+ Add this line to your application's Gemfile:
24
+
25
+ ```ruby
26
+ gem 'entrata'
27
+ ```
28
+
29
+ And then execute:
30
+
31
+ $ bundle
32
+
33
+ Or install it yourself as:
34
+
35
+ $ gem install entrata
36
+
37
+ ## Usage
38
+
39
+ ### Configuration
40
+
41
+ No gem configuration, but getting an access token (via `Entrata::Client.get_access_token`) requires `client_id` and `client_secret` in addition to `auth_code` provided by Entrata on property activation.
42
+
43
+ ### API Resources
44
+
45
+ All API access is performed via the `Entrata::Client` class.
46
+
47
+ There are two usage patterns:
48
+ 1. Gathering client credential and PMC subdomain
49
+ 2. Making authenticated requests to API resources using saved credential and PMC subdomain
50
+
51
+ #### Gathering client credential and PMC subdomain
52
+ Class methods are available for convenience of this one-time call sequence per PMC
53
+ ```
54
+ auth_code = 'received from activation endpoint'
55
+ client_id = ENV['ENTRATA_CLIENT_ID'] # Apartmentlist assigned client_id
56
+ client_secret = ENV['ENTRATA_CLIENT_SECRET'] # Apartmentlist current client_secret
57
+
58
+ token_hash = Entrata::Client.get_access_token(auth_code: auth_code,
59
+ client_id: client_id,
60
+ client_secret: client_secret)
61
+
62
+ client_info = Entrata::Client.get_client_subdomain(token_hash['token'])
63
+ subdomain = client_info['subdomain']
64
+
65
+ # create or update Entrata credential with token and subdomain
66
+ ```
67
+
68
+ #### Making authenticated requests to API resources
69
+ An instance of the `Entrata::Client` may be created per PMC to request authenticated resources available under its subdomain.
70
+
71
+ ```
72
+ credential = #find by PMC or iterate all entrata credentials
73
+ token = credential.fields['access_token']
74
+ subdomain = credential.fields['subdomain']
75
+
76
+ client = Entrata::Client.new(subdomain: subdomain, token: token)
77
+
78
+ remote_property_id = #get from activation
79
+
80
+ client.process_property_activation(remote_property_id) #defaults to 'approve'-ing the activation
81
+ property_hash = client.get_property_info(remote_property_id)
82
+ multiple_property_hash = client.get_ils_properties_data([remote_property_id])
83
+ ```
84
+
85
+ ## Testing
86
+
87
+ A test client is available for integration testing in your consuming application.
88
+
89
+ `Entrata::TestClient` behaves like `Entrata::Client`, but returns static responses or raises errors just like the real client would. It never makes any real HTTP requests.
90
+
91
+ `Entrata::TestClient` defaults to providing successful (static) responses collected from the real API.
92
+
93
+ You may force failure by providing specific ("fail") input values. See below for specific trigger parameters:
94
+ ```
95
+ # Providing "fail" to specific parameters will raise `Entrata::Request::Error` just like the real client would.
96
+ Entrata::TestClient.get_access_token(auth_code: 'fail', client_id: 'anything', client_secret: 'anything') #BOOM!
97
+ Entrata::TestClient.get_client_info('fail') #BOOM!
98
+
99
+ test_client = Entrata::TestClient.new(subdomain: 'anything', token: 'anything')
100
+ test_client.get_property_info('fail') #BOOM!
101
+ test_client.process_property_activation('fail') #BOOM!
102
+ test_client.get_ils_properties_data(['at', 'least', 'one', 'fail']) #BOOM!
103
+
104
+ ```
105
+
106
+ ## Development
107
+
108
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
109
+
110
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+ Rake.add_rakelib('./lib/tasks')
6
+
7
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'pry'
5
+ require 'entrata'
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require 'pry'
12
+ # Pry.start
13
+
14
+ require 'irb'
15
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/entrata.gemspec ADDED
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'entrata/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'entrata'
8
+ spec.version = Entrata::VERSION
9
+ spec.authors = ['Justin Richard']
10
+ spec.email = ['justin@apartmentlist.com']
11
+
12
+ spec.summary = 'Ruby client of Entrata API'
13
+ spec.homepage = 'https://github.com/apartmentlist/entrata'
14
+
15
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
16
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
17
+ spec.require_paths = ['lib']
18
+
19
+ spec.add_runtime_dependency 'activesupport', '~> 5.2'
20
+ spec.add_runtime_dependency 'faraday', '< 1.0'
21
+
22
+ spec.add_development_dependency 'bundler'
23
+ spec.add_development_dependency 'pry-byebug', '~> 3.3'
24
+ spec.add_development_dependency 'rake'
25
+ spec.add_development_dependency 'rspec', '~> 3.0'
26
+ spec.add_development_dependency 'webmock', '~> 1.21'
27
+ end
@@ -0,0 +1,152 @@
1
+ require 'faraday'
2
+ require 'uri'
3
+ require 'entrata/request/get_access_token'
4
+ require 'entrata/request/get_client_info'
5
+ require 'entrata/request/get_ils_properties_data'
6
+ require 'entrata/request/get_property_info'
7
+ require 'entrata/request/process_property_activation'
8
+ require 'entrata/request/send_lead_details'
9
+ require 'entrata/request/send_inactive_leads'
10
+ require 'entrata/request/send_reactivate_leads'
11
+
12
+ # Client to access all API resources through
13
+ module Entrata
14
+ class Client
15
+ def initialize(subdomain:, token:)
16
+ @subdomain = subdomain
17
+ @token = token
18
+ end
19
+
20
+ class << self
21
+ def get_access_token(auth_code:, client_id:, client_secret:,
22
+ conn: Faraday.new('https://sync.entrata.com'))
23
+ Request::GetAccessToken.new(
24
+ conn: conn,
25
+ auth_code: auth_code,
26
+ client_id: client_id,
27
+ client_secret: client_secret
28
+ ).perform
29
+ end
30
+
31
+ # requires token directly; typical use is to #get_access_token, then
32
+ # #get_client_info immediately with the same client
33
+ def get_client_info(token, conn: Faraday.new('https://sync.entrata.com'))
34
+ conn.authorization :Bearer, token
35
+
36
+ Request::GetClientInfo.new(conn: conn).perform
37
+ end
38
+ end
39
+
40
+ def get_property_info(property_id)
41
+ Request::GetPropertyInfo.new(
42
+ conn: conn,
43
+ property_id: property_id
44
+ ).perform
45
+ end
46
+
47
+ def process_property_activation(property_id, activation_status: 'approve')
48
+ Request::ProcessPropertyActivation.new(
49
+ conn: conn,
50
+ property_id: property_id,
51
+ activation_status: activation_status
52
+ ).perform
53
+ end
54
+
55
+ def get_ils_properties_data(property_ids)
56
+ Request::GetIlsPropertiesData.new(
57
+ conn: conn,
58
+ property_ids: property_ids
59
+ ).perform_with_curl
60
+ end
61
+
62
+ def send_lead_details(customer:, property_id:, preferences: nil)
63
+ Request::SendLeadDetails.new(
64
+ conn: conn,
65
+ customer: customer,
66
+ preferences: preferences,
67
+ property_id: property_id
68
+ ).perform
69
+ end
70
+
71
+ def send_inactive_leads(customer:, lead_source_id:, leasing_agent_id:, preferences:, property_id:)
72
+ Request::SendInactiveLeads.new(
73
+ conn: basic_auth_conn,
74
+ customer: customer,
75
+ lead_source_id: lead_source_id,
76
+ leasing_agent_id: leasing_agent_id,
77
+ preferences: preferences,
78
+ property_id: property_id
79
+ ).perform
80
+ end
81
+
82
+ def send_reactivate_leads(applicant_id:, application_id:, customer:, preferences:, property_id:)
83
+ Request::SendReactivateLeads.new(
84
+ conn: basic_auth_conn,
85
+ applicant_id: applicant_id,
86
+ application_id: application_id,
87
+ customer: customer,
88
+ preferences: preferences,
89
+ property_id: property_id
90
+ ).perform
91
+ end
92
+
93
+ def lead_details_request_body(customer:, property_id:, preferences: nil)
94
+ Request::SendLeadDetails.new(
95
+ conn: conn,
96
+ customer: customer,
97
+ preferences: preferences,
98
+ property_id: property_id
99
+ ).body
100
+ end
101
+
102
+ def inactive_lead_request_body(customer:, lead_source_id:, leasing_agent_id:, preferences:, property_id:)
103
+ Request::SendInactiveLeads.new(
104
+ conn: basic_auth_conn,
105
+ customer: customer,
106
+ lead_source_id: lead_source_id,
107
+ leasing_agent_id: leasing_agent_id,
108
+ preferences: preferences,
109
+ property_id: property_id
110
+ ).body
111
+ end
112
+
113
+ def reactivate_lead_request_body(applicant_id:, application_id:, customer:, preferences:, property_id:)
114
+ Request::SendReactivateLeads.new(
115
+ conn: basic_auth_conn,
116
+ applicant_id: applicant_id,
117
+ application_id: application_id,
118
+ customer: customer,
119
+ preferences: preferences,
120
+ property_id: property_id
121
+ ).body
122
+ end
123
+
124
+ private
125
+
126
+ def url
127
+ URI::HTTPS.build(host: "#{subdomain}.entrata.com").to_s
128
+ end
129
+
130
+ def conn
131
+ @conn_with_auth ||= Faraday.new(url) do |request|
132
+ # MUST specify adapter when configuring with block! Otherwise no adapter
133
+ # will be set: https://github.com/lostisland/faraday/issues/121
134
+ request.adapter Faraday.default_adapter
135
+ request.authorization :Bearer, token
136
+ request.options[:timeout] = 600 # 10 minutes
137
+ end
138
+ end
139
+
140
+ def basic_auth_conn
141
+ @basic_auth_conn ||= Faraday.new(url) do |request|
142
+ # MUST specify adapter when configuring with block! Otherwise no adapter
143
+ # will be set: https://github.com/lostisland/faraday/issues/121
144
+ request.adapter Faraday.default_adapter
145
+ request.authorization :Basic, token
146
+ request.options[:timeout] = 600 # 10 minutes
147
+ end
148
+ end
149
+
150
+ attr_reader :token, :subdomain
151
+ end
152
+ end
@@ -0,0 +1,20 @@
1
+ module Entrata
2
+ module Parameter
3
+ # Wraps the user-related data that we pass to Entrata. All of the Customer
4
+ # attributes are required when inserting a lead.
5
+ class Customer
6
+ attr_reader :email, :first_name, :last_name, :phone
7
+
8
+ # @param email [String] Renter email
9
+ # @param first_name [String] Renter first name
10
+ # @param last_name [String] Renter last name
11
+ # @param phone [Integer] Renter phone number, digits only
12
+ def initialize(email:, first_name:, last_name:, phone:)
13
+ @email = email
14
+ @first_name = first_name
15
+ @last_name = last_name
16
+ @phone = phone
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,61 @@
1
+ module Entrata
2
+ module Parameter
3
+ # Wraps the customer preference data that we pass to Entrata. All of the
4
+ # preference attributes are optional when inserting a lead.
5
+ class CustomerPreferences
6
+ attr_reader :beds,
7
+ :baths,
8
+ :comments,
9
+ :lease_terms,
10
+ :move_in_date,
11
+ :number_of_pets,
12
+ :preferred_floorplan_id,
13
+ :preferred_unit_id,
14
+ :min_price,
15
+ :max_price
16
+
17
+ # @param beds [Integer] Number of beds the renter is interested in
18
+ # @param baths [Float] Number of baths the renter is interested in
19
+ # @param comments [String] Notes about the renter's preferences plus any
20
+ # information about a tour if there is one, plus the renter's message
21
+ # for the property. Entrata only gives us one key for comments, so we
22
+ # need to put everything there.
23
+ # @param lease_terms [String] Length of preferred lease term,
24
+ # e.g. '12 months'
25
+ # @param move_in_date [Date] Renter move in date
26
+ # @param number_of_pets [String] The number of pets the renter has
27
+ # @param preferred_floorplan_id [Integer] Entrata's ID for the floorplan
28
+ # the renter is interested in. They do not verify that this ID matches
29
+ # any real floorplan on the property in their system.
30
+ # @param preferred_unit_id [Integer] Entrata's ID for the unit the renter
31
+ # is interested in. They do not verify that this ID matches any real
32
+ # unit on the property in their system.
33
+ # @param min_price [Float] Renter's minimum budget
34
+ # @param max_price [Float] Renter's maximum budget
35
+ def initialize(
36
+ beds: nil,
37
+ baths: nil,
38
+ comments: nil,
39
+ lease_terms: nil,
40
+ move_in_date: nil,
41
+ number_of_pets: nil,
42
+ preferred_floorplan_id: nil,
43
+ preferred_unit_id: nil,
44
+ min_price: nil,
45
+ max_price: nil
46
+ )
47
+
48
+ @beds = beds
49
+ @baths = baths
50
+ @comments = comments
51
+ @lease_terms = lease_terms
52
+ @move_in_date = move_in_date
53
+ @number_of_pets = number_of_pets
54
+ @preferred_floorplan_id = preferred_floorplan_id
55
+ @preferred_unit_id = preferred_unit_id
56
+ @min_price = min_price
57
+ @max_price = max_price
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,117 @@
1
+ require 'faraday'
2
+ require 'json'
3
+ require 'entrata/request/error'
4
+ require 'open3'
5
+
6
+ module Entrata
7
+ module Request
8
+ class Base
9
+ def initialize(conn:, **other_args)
10
+ @conn = conn
11
+ set_content_header!
12
+
13
+ after_initialize(**other_args)
14
+ end
15
+
16
+ # Initialization call back for subclasses init parameter access
17
+ def after_initialize(**params)
18
+ # No-op, this method is a call back for subclasses
19
+ end
20
+
21
+ def resource_name
22
+ raise NotImplementedError, 'implement with exact case of resource'
23
+ end
24
+
25
+ def resource_path
26
+ '/api/ils/internetlisting'
27
+ end
28
+
29
+ def resource_auth
30
+ { type: 'oauth' }
31
+ end
32
+
33
+ def resource_params
34
+ {}
35
+ end
36
+
37
+ def perform
38
+ response = conn.post(resource_path) do |request|
39
+ request.body = body
40
+ end
41
+
42
+ result_hash = try_parsing_body(response.body)
43
+
44
+ if response.success? && result_hash
45
+ result_hash
46
+ else
47
+ error = create_response_error(response.body)
48
+ raise error
49
+ end
50
+ end
51
+
52
+ def perform_with_curl
53
+ stdout, _stderr, status = Open3.capture3(curl_command,)
54
+ result_hash = try_parsing_body(stdout)
55
+
56
+ if status == 0 && result_hash
57
+ result_hash
58
+ else
59
+ error = create_response_error(stdout)
60
+ raise error
61
+ end
62
+ end
63
+
64
+ def body
65
+ {
66
+ auth: resource_auth,
67
+ method: {
68
+ name: resource_name,
69
+ params: resource_params
70
+ }
71
+ }.to_json
72
+ end
73
+
74
+ private
75
+
76
+ def try_parsing_body(raw_body)
77
+ @parsed_body = JSON.parse(raw_body)
78
+ @parsed_body.fetch('response', {}).fetch('result', nil)
79
+ rescue JSON::ParserError
80
+ nil
81
+ end
82
+
83
+ def create_response_error(message)
84
+ error_hash = try_parsing_error_hash(message)
85
+ if error_hash
86
+ Entrata::Request::Error.new(error_hash['message'], error_hash['code'])
87
+ else
88
+ Entrata::Request::Error.new(message)
89
+ end
90
+ end
91
+
92
+ def try_parsing_error_hash(error)
93
+ parsed_error = JSON.parse(error)
94
+ parsed_error.fetch('response', {}).fetch('error', nil)
95
+ rescue JSON::ParserError
96
+ nil
97
+ end
98
+
99
+ def set_content_header!
100
+ conn.headers['Content-Type'] = 'application/json'
101
+ end
102
+
103
+ def curl_command
104
+ full_url =
105
+ URI::HTTPS.build(host: conn.url_prefix.host, path: resource_path)
106
+
107
+ <<-CURL_COMMAND.gsub!(/\s+/, ' ').strip
108
+ curl #{full_url.to_s} -X POST -H 'Content-Type: application/json'
109
+ -H 'Authorization: #{conn.headers['Authorization']}'
110
+ --data '#{body}'
111
+ CURL_COMMAND
112
+ end
113
+
114
+ attr_reader :conn, :hostname, :parsed_body
115
+ end
116
+ end
117
+ end