open_api-loader 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +15 -0
  3. data/.gitignore +13 -0
  4. data/.rspec +3 -0
  5. data/.rubocop.yml +28 -0
  6. data/.travis.yml +24 -0
  7. data/CHANGELOG.md +10 -0
  8. data/Gemfile +8 -0
  9. data/LICENSE.txt +21 -0
  10. data/README.md +99 -0
  11. data/Rakefile +10 -0
  12. data/bin/console +6 -0
  13. data/bin/setup +6 -0
  14. data/lib/open_api-loader.rb +1 -0
  15. data/lib/open_api/loader.rb +44 -0
  16. data/lib/open_api/loader/collector.rb +61 -0
  17. data/lib/open_api/loader/denormalizer.rb +27 -0
  18. data/lib/open_api/loader/denormalizer/parameters.rb +46 -0
  19. data/lib/open_api/loader/denormalizer/security.rb +35 -0
  20. data/lib/open_api/loader/denormalizer/servers.rb +36 -0
  21. data/lib/open_api/loader/denormalizer/variables.rb +54 -0
  22. data/lib/open_api/loader/reader.rb +47 -0
  23. data/lib/open_api/loader/ref.rb +85 -0
  24. data/lib/open_api/loader/translator.rb +50 -0
  25. data/lib/open_api/loader/translator/clean_definitions.rb +16 -0
  26. data/lib/open_api/loader/translator/convert_bodies.rb +77 -0
  27. data/lib/open_api/loader/translator/convert_forms.rb +71 -0
  28. data/lib/open_api/loader/translator/convert_parameters.rb +135 -0
  29. data/lib/open_api/loader/translator/convert_responses.rb +63 -0
  30. data/lib/open_api/loader/translator/convert_security_schemes.rb +49 -0
  31. data/lib/open_api/loader/translator/convert_servers.rb +46 -0
  32. data/lib/open_api/loader/translator/convert_version.rb +12 -0
  33. data/lib/open_api/loader/translator/denormalize_consumes.rb +44 -0
  34. data/lib/open_api/loader/translator/denormalize_parameters.rb +55 -0
  35. data/lib/open_api/loader/translator/denormalize_produces.rb +53 -0
  36. data/open_api-loader.gemspec +25 -0
  37. data/spec/fixtures/oas2/collected.yaml +1012 -0
  38. data/spec/fixtures/oas2/denormalized.yaml +1462 -0
  39. data/spec/fixtures/oas2/loaded.yaml +564 -0
  40. data/spec/fixtures/oas2/models.yaml +118 -0
  41. data/spec/fixtures/oas2/source.json +1 -0
  42. data/spec/fixtures/oas2/source.yaml +569 -0
  43. data/spec/fixtures/oas2/translated.yaml +1396 -0
  44. data/spec/fixtures/oas3/collected.yaml +233 -0
  45. data/spec/fixtures/oas3/denormalized.yaml +233 -0
  46. data/spec/fixtures/oas3/source.json +1 -0
  47. data/spec/fixtures/oas3/source.yaml +217 -0
  48. data/spec/loader_spec.rb +53 -0
  49. data/spec/spec_helper.rb +17 -0
  50. data/spec/support/fixture_helpers.rb +18 -0
  51. data/spec/support/path_helpers.rb +27 -0
  52. data/spec/unit/collector_spec.rb +31 -0
  53. data/spec/unit/denormalizer_spec.rb +17 -0
  54. data/spec/unit/reader_spec.rb +53 -0
  55. data/spec/unit/ref_spec.rb +107 -0
  56. data/spec/unit/translator_spec.rb +15 -0
  57. metadata +232 -0
@@ -0,0 +1 @@
1
+ {"openapi":"3.0.0","servers":[{"url":"{scheme}://developer.uspto.gov/ds-api","variables":{"scheme":{"description":"The Data Set API is accessible via https and http","enum":["https","http"],"default":"https"}}}],"info":{"description":"The Data Set API (DSAPI) allows the public users to discover and search USPTO exported data sets. This is a generic API that allows USPTO users to make any CSV based data files searchable through API. With the help of GET call, it returns the list of data fields that are searchable. With the help of POST call, data can be fetched based on the filters on the field names. Please note that POST call is used to search the actual data. The reason for the POST call is that it allows users to specify any complex search criteria without worry about the GET size limitations as well as encoding of the input parameters.","version":"1.0.0","title":"USPTO Data Set API","contact":{"name":"Open Data Portal","url":"https://developer.uspto.gov","email":"developer@uspto.gov"}},"tags":[{"name":"metadata","description":"Find out about the data sets"},{"name":"search","description":"Search a data set"}],"paths":{"/":{"get":{"tags":["metadata"],"operationId":"list-data-sets","summary":"List available data sets","responses":{"200":{"description":"Returns a list of data sets","content":{"application/json":{"schema":{"$ref":"#/components/schemas/dataSetList"},"example":{"total":2,"apis":[{"apiKey":"oa_citations","apiVersionNumber":"v1","apiUrl":"https://developer.uspto.gov/ds-api/oa_citations/v1/fields","apiDocumentationUrl":"https://developer.uspto.gov/ds-api-docs/index.html?url=https://developer.uspto.gov/ds-api/swagger/docs/oa_citations.json"},{"apiKey":"cancer_moonshot","apiVersionNumber":"v1","apiUrl":"https://developer.uspto.gov/ds-api/cancer_moonshot/v1/fields","apiDocumentationUrl":"https://developer.uspto.gov/ds-api-docs/index.html?url=https://developer.uspto.gov/ds-api/swagger/docs/cancer_moonshot.json"}]}}}}}}},"/{dataset}/{version}/fields":{"parameters":[{"name":"dataset","in":"path","description":"Name of the dataset. In this case, the default value is oa_citations","required":true,"schema":{"type":"string","default":"oa_citations"}},{"name":"version","in":"path","description":"Version of the dataset.","required":true,"schema":{"type":"string","default":"v1"}}],"get":{"tags":["metadata"],"summary":"Provides the general information about the API and the list of fields that can be used to query the dataset.","description":"This GET API returns the list of all the searchable field names that are in the oa_citations. Please see the 'fields' attribute which returns an array of field names. Each field or a combination of fields can be searched using the syntax options shown below.","operationId":"list-searchable-fields","responses":{"200":{"description":"The dataset api for the given version is found and it is accessible to consume.","content":{"application/json":{"schema":{"type":"string"}}}},"404":{"description":"The combination of dataset name and version is not found in the system or it is not published yet to be consumed by public.","content":{"application/json":{"schema":{"type":"string"}}}}}}},"/{dataset}/{version}/records":{"parameters":[{"name":"version","in":"path","description":"Version of the dataset.","required":true,"schema":{"type":"string","default":"v1"}},{"name":"dataset","in":"path","description":"Name of the dataset. In this case, the default value is oa_citations","required":true,"schema":{"type":"string","default":"oa_citations"}}],"post":{"tags":["search"],"summary":"Provides search capability for the data set with the given search criteria.","description":"This API is based on Solr/Lucense Search. The data is indexed using SOLR. This GET API returns the list of all the searchable field names that are in the Solr Index. Please see the 'fields' attribute which returns an array of field names. Each field or a combination of fields can be searched using the Solr/Lucene Syntax. Please refer https://lucene.apache.org/core/3_6_2/queryparsersyntax.html#Overview for the query syntax. List of field names that are searchable can be determined using above GET api.","operationId":"perform-search","responses":{"200":{"description":"successful operation","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":{"type":"object"}}}}}},"404":{"description":"No matching record found for the given criteria."}},"requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"criteria":{"description":"Uses Lucene Query Syntax in the format of propertyName:value, propertyName:[num1 TO num2] and date range format: propertyName:[yyyyMMdd TO yyyyMMdd]. In the response please see the 'docs' element which has the list of record objects. Each record structure would consist of all the fields and their corresponding values.","type":"string","default":"*:*"},"start":{"description":"Starting record number. Default value is 0.","type":"integer","default":0},"rows":{"description":"Specify number of rows to be returned. If you run the search with default values, in the response you will see 'numFound' attribute which will tell the number of records available in the dataset.","type":"integer","default":100}},"required":["criteria"]}}}}}}},"components":{"schemas":{"dataSetList":{"type":"object","properties":{"total":{"type":"integer"},"apis":{"type":"array","items":{"type":"object","properties":{"apiKey":{"type":"string","description":"To be used as a dataset parameter value"},"apiVersionNumber":{"type":"string","description":"To be used as a version parameter value"},"apiUrl":{"type":"string","format":"uriref","description":"The URL describing the dataset's fields"},"apiDocumentationUrl":{"type":"string","format":"uriref","description":"A URL to the API console for each API"}}}}}}}}}
@@ -0,0 +1,217 @@
1
+ #
2
+ # The OAS3 specification downloaded from
3
+ # https://github.com/OAI/OpenAPI-Specification/blob/master/examples/v3.0/uspto.yaml
4
+ #
5
+ # The source.json contain exactly the same data in JSON format
6
+ #
7
+ ---
8
+ openapi: 3.0.0
9
+ servers:
10
+ - url: '{scheme}://developer.uspto.gov/ds-api'
11
+ variables:
12
+ scheme:
13
+ description: 'The Data Set API is accessible via https and http'
14
+ enum:
15
+ - 'https'
16
+ - 'http'
17
+ default: 'https'
18
+ info:
19
+ description: >-
20
+ The Data Set API (DSAPI) allows the public users to discover and search
21
+ USPTO exported data sets. This is a generic API that allows USPTO users to
22
+ make any CSV based data files searchable through API. With the help of GET
23
+ call, it returns the list of data fields that are searchable. With the help
24
+ of POST call, data can be fetched based on the filters on the field names.
25
+ Please note that POST call is used to search the actual data. The reason for
26
+ the POST call is that it allows users to specify any complex search criteria
27
+ without worry about the GET size limitations as well as encoding of the
28
+ input parameters.
29
+ version: 1.0.0
30
+ title: USPTO Data Set API
31
+ contact:
32
+ name: Open Data Portal
33
+ url: 'https://developer.uspto.gov'
34
+ email: developer@uspto.gov
35
+ tags:
36
+ - name: metadata
37
+ description: Find out about the data sets
38
+ - name: search
39
+ description: Search a data set
40
+ paths:
41
+ /:
42
+ get:
43
+ tags:
44
+ - metadata
45
+ operationId: list-data-sets
46
+ summary: List available data sets
47
+ responses:
48
+ 200:
49
+ description: Returns a list of data sets
50
+ content:
51
+ application/json:
52
+ schema:
53
+ $ref: '#/components/schemas/dataSetList'
54
+ example:
55
+ {
56
+ "total": 2,
57
+ "apis": [
58
+ {
59
+ "apiKey": "oa_citations",
60
+ "apiVersionNumber": "v1",
61
+ "apiUrl": "https://developer.uspto.gov/ds-api/oa_citations/v1/fields",
62
+ "apiDocumentationUrl": "https://developer.uspto.gov/ds-api-docs/index.html?url=https://developer.uspto.gov/ds-api/swagger/docs/oa_citations.json"
63
+ },
64
+ {
65
+ "apiKey": "cancer_moonshot",
66
+ "apiVersionNumber": "v1",
67
+ "apiUrl": "https://developer.uspto.gov/ds-api/cancer_moonshot/v1/fields",
68
+ "apiDocumentationUrl": "https://developer.uspto.gov/ds-api-docs/index.html?url=https://developer.uspto.gov/ds-api/swagger/docs/cancer_moonshot.json"
69
+ }
70
+ ]
71
+ }
72
+ /{dataset}/{version}/fields:
73
+ parameters:
74
+ - name: dataset
75
+ in: path
76
+ description: 'Name of the dataset. In this case, the default value is oa_citations'
77
+ required: true
78
+ schema:
79
+ type: string
80
+ default: oa_citations
81
+ - name: version
82
+ in: path
83
+ description: Version of the dataset.
84
+ required: true
85
+ schema:
86
+ type: string
87
+ default: v1
88
+ get:
89
+ tags:
90
+ - metadata
91
+ summary: >-
92
+ Provides the general information about the API and the list of fields
93
+ that can be used to query the dataset.
94
+ description: >-
95
+ This GET API returns the list of all the searchable field names that are
96
+ in the oa_citations. Please see the 'fields' attribute which returns an
97
+ array of field names. Each field or a combination of fields can be
98
+ searched using the syntax options shown below.
99
+ operationId: list-searchable-fields
100
+ responses:
101
+ 200:
102
+ description: >-
103
+ The dataset api for the given version is found and it is accessible
104
+ to consume.
105
+ content:
106
+ application/json:
107
+ schema:
108
+ type: string
109
+ 404:
110
+ description: >-
111
+ The combination of dataset name and version is not found in the
112
+ system or it is not published yet to be consumed by public.
113
+ content:
114
+ application/json:
115
+ schema:
116
+ type: string
117
+ /{dataset}/{version}/records:
118
+ parameters:
119
+ - name: version
120
+ in: path
121
+ description: Version of the dataset.
122
+ required: true
123
+ schema:
124
+ type: string
125
+ default: v1
126
+ - name: dataset
127
+ in: path
128
+ description: 'Name of the dataset. In this case, the default value is oa_citations'
129
+ required: true
130
+ schema:
131
+ type: string
132
+ default: oa_citations
133
+ post:
134
+ tags:
135
+ - search
136
+ summary: >-
137
+ Provides search capability for the data set with the given search
138
+ criteria.
139
+ description: >-
140
+ This API is based on Solr/Lucense Search. The data is indexed using
141
+ SOLR. This GET API returns the list of all the searchable field names
142
+ that are in the Solr Index. Please see the 'fields' attribute which
143
+ returns an array of field names. Each field or a combination of fields
144
+ can be searched using the Solr/Lucene Syntax. Please refer
145
+ https://lucene.apache.org/core/3_6_2/queryparsersyntax.html#Overview for
146
+ the query syntax. List of field names that are searchable can be
147
+ determined using above GET api.
148
+ operationId: perform-search
149
+ responses:
150
+ 200:
151
+ description: successful operation
152
+ content:
153
+ application/json:
154
+ schema:
155
+ type: array
156
+ items:
157
+ type: object
158
+ additionalProperties:
159
+ type: object
160
+ 404:
161
+ description: No matching record found for the given criteria.
162
+ requestBody:
163
+ content:
164
+ application/x-www-form-urlencoded:
165
+ schema:
166
+ type: object
167
+ properties:
168
+ criteria:
169
+ description: >-
170
+ Uses Lucene Query Syntax in the format of
171
+ propertyName:value, propertyName:[num1 TO num2] and date
172
+ range format: propertyName:[yyyyMMdd TO yyyyMMdd]. In the
173
+ response please see the 'docs' element which has the list of
174
+ record objects. Each record structure would consist of all
175
+ the fields and their corresponding values.
176
+ type: string
177
+ default: '*:*'
178
+ start:
179
+ description: Starting record number. Default value is 0.
180
+ type: integer
181
+ default: 0
182
+ rows:
183
+ description: >-
184
+ Specify number of rows to be returned. If you run the search
185
+ with default values, in the response you will see 'numFound'
186
+ attribute which will tell the number of records available in
187
+ the dataset.
188
+ type: integer
189
+ default: 100
190
+ required:
191
+ - criteria
192
+ components:
193
+ schemas:
194
+ dataSetList:
195
+ type: object
196
+ properties:
197
+ total:
198
+ type: integer
199
+ apis:
200
+ type: array
201
+ items:
202
+ type: object
203
+ properties:
204
+ apiKey:
205
+ type: string
206
+ description: To be used as a dataset parameter value
207
+ apiVersionNumber:
208
+ type: string
209
+ description: To be used as a version parameter value
210
+ apiUrl:
211
+ type: string
212
+ format: uriref
213
+ description: "The URL describing the dataset's fields"
214
+ apiDocumentationUrl:
215
+ type: string
216
+ format: uriref
217
+ description: A URL to the API console for each API
@@ -0,0 +1,53 @@
1
+ RSpec.describe OpenAPI::Loader do
2
+ subject { described_class.call(source, **options) }
3
+
4
+ let(:options) { {} }
5
+
6
+ context "with OAS 2" do
7
+ context "in YAML" do
8
+ let(:source) { "spec/fixtures/oas2/source.yaml" }
9
+ let(:target) { yaml_fixture_file "oas2/denormalized.yaml" }
10
+
11
+ it { is_expected.to eq target }
12
+ end
13
+
14
+ context "in JSON" do
15
+ let(:source) { "spec/fixtures/oas2/source.json" }
16
+ let(:target) { yaml_fixture_file "oas2/denormalized.yaml" }
17
+
18
+ it { is_expected.to eq target }
19
+ end
20
+
21
+ context "with option denormalize: false" do
22
+ let(:source) { "spec/fixtures/oas2/source.yaml" }
23
+ let(:target) { yaml_fixture_file "oas2/translated.yaml" }
24
+ let(:options) { { denormalize: false } }
25
+
26
+ it { is_expected.to eq target }
27
+ end
28
+ end
29
+
30
+ context "with OAS 3" do
31
+ context "in YAML" do
32
+ let(:source) { "spec/fixtures/oas3/source.yaml" }
33
+ let(:target) { yaml_fixture_file "oas3/denormalized.yaml" }
34
+
35
+ it { is_expected.to eq target }
36
+ end
37
+
38
+ context "in JSON" do
39
+ let(:source) { "spec/fixtures/oas3/source.json" }
40
+ let(:target) { yaml_fixture_file "oas3/denormalized.yaml" }
41
+
42
+ it { is_expected.to eq target }
43
+ end
44
+
45
+ context "with option denormalize: false" do
46
+ let(:source) { "spec/fixtures/oas3/source.yaml" }
47
+ let(:target) { yaml_fixture_file "oas3/collected.yaml" }
48
+ let(:options) { { denormalize: false } }
49
+
50
+ it { is_expected.to eq target }
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,17 @@
1
+ require "bundler/setup"
2
+ require "pry"
3
+ require "webmock/rspec"
4
+ require "open_api/loader"
5
+ require "rspec/its"
6
+
7
+ require_relative "support/fixture_helpers"
8
+ require_relative "support/path_helpers"
9
+
10
+ RSpec.configure do |config|
11
+ config.example_status_persistence_file_path = ".rspec_status"
12
+ config.expect_with(:rspec) { |c| c.syntax = :expect }
13
+
14
+ config.order = :random
15
+ config.filter_run focus: true
16
+ config.run_all_when_everything_filtered = true
17
+ end
@@ -0,0 +1,18 @@
1
+ require "yaml"
2
+ require "json"
3
+
4
+ def fixture_file_path(filename)
5
+ File.expand_path "spec/fixtures/#{filename}"
6
+ end
7
+
8
+ def read_fixture_file(filename)
9
+ File.read fixture_file_path(filename)
10
+ end
11
+
12
+ def yaml_fixture_file(filename, **_params)
13
+ YAML.load read_fixture_file(filename)
14
+ end
15
+
16
+ def json_fixture_file(filename)
17
+ JSON.parse read_fixture_file(filename)
18
+ end
@@ -0,0 +1,27 @@
1
+ def local_path(content)
2
+ file = Tempfile.create ""
3
+ File.write file, content
4
+ file.path
5
+ end
6
+
7
+ def remote_path(content)
8
+ path = "https://www.example.com/source"
9
+ stub_request(:get, path).to_return(body: content)
10
+ path
11
+ end
12
+
13
+ def yaml_local_path(data)
14
+ local_path YAML.dump(data)
15
+ end
16
+
17
+ def yaml_remote_path(data)
18
+ remote_path YAML.dump(data)
19
+ end
20
+
21
+ def json_local_path(data)
22
+ local_path JSON.dump(data)
23
+ end
24
+
25
+ def json_remote_path(data)
26
+ remote_path JSON.dump(data)
27
+ end
@@ -0,0 +1,31 @@
1
+ RSpec.describe OpenAPI::Loader::Collector, ".call" do
2
+ subject { described_class.call(source) }
3
+
4
+ context "OAS 2" do
5
+ let(:target) { yaml_fixture_file "oas2/collected.yaml" }
6
+
7
+ context "from local yaml" do
8
+ let(:source) { "spec/fixtures/oas2/source.yaml" }
9
+ it { is_expected.to eq target }
10
+ end
11
+
12
+ context "from local json" do
13
+ let(:source) { "spec/fixtures/oas2/source.json" }
14
+ it { is_expected.to eq target }
15
+ end
16
+ end
17
+
18
+ context "OAS 3" do
19
+ let(:target) { yaml_fixture_file "oas3/collected.yaml" }
20
+
21
+ context "from local yaml" do
22
+ let(:source) { "spec/fixtures/oas3/source.yaml" }
23
+ it { is_expected.to eq target }
24
+ end
25
+
26
+ context "from local json" do
27
+ let(:source) { "spec/fixtures/oas3/source.json" }
28
+ it { is_expected.to eq target }
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,17 @@
1
+ RSpec.describe OpenAPI::Loader::Denormalizer, ".call" do
2
+ subject { described_class.call(source) }
3
+
4
+ context "OAS 2" do
5
+ let(:source) { yaml_fixture_file "oas2/translated.yaml" }
6
+ let(:target) { yaml_fixture_file "oas2/denormalized.yaml" }
7
+
8
+ it { is_expected.to eq target }
9
+ end
10
+
11
+ context "OAS 3" do
12
+ let(:source) { yaml_fixture_file "oas3/collected.yaml" }
13
+ let(:target) { yaml_fixture_file "oas3/denormalized.yaml" }
14
+
15
+ it { is_expected.to eq target }
16
+ end
17
+ end
@@ -0,0 +1,53 @@
1
+ RSpec.describe OpenAPI::Loader::Reader, ".call" do
2
+ subject { described_class.call(source) }
3
+
4
+ context "from local yaml" do
5
+ let(:source) { "spec/fixtures/oas2/source.yaml" }
6
+ let(:target) { yaml_fixture_file "oas2/loaded.yaml" }
7
+
8
+ it { is_expected.to eq target }
9
+ end
10
+
11
+ context "from local json" do
12
+ let(:source) { "spec/fixtures/oas2/source.json" }
13
+ let(:target) { yaml_fixture_file "oas2/loaded.yaml" }
14
+
15
+ it { is_expected.to eq target }
16
+ end
17
+
18
+ context "from another existing local" do
19
+ let(:source) { local_path target }
20
+ let(:target) { "foo" }
21
+
22
+ it { is_expected.to eq target }
23
+ end
24
+
25
+ context "from absent local" do
26
+ let(:source) { "foo" }
27
+
28
+ it "raises StandardError" do
29
+ expect { subject }.to raise_error(StandardError)
30
+ end
31
+ end
32
+
33
+ context "from remote yaml" do
34
+ let(:source) { yaml_remote_path(target) }
35
+ let(:target) { { "foo" => "bar" } }
36
+
37
+ it { is_expected.to eq target }
38
+ end
39
+
40
+ context "from remote json" do
41
+ let(:source) { json_remote_path(target) }
42
+ let(:target) { { "foo" => "bar" } }
43
+
44
+ it { is_expected.to eq target }
45
+ end
46
+
47
+ context "from another remote" do
48
+ let(:source) { remote_path(target) }
49
+ let(:target) { "foo" }
50
+
51
+ it { is_expected.to eq target }
52
+ end
53
+ end
@@ -0,0 +1,107 @@
1
+ RSpec.describe OpenAPI::Loader::Ref do
2
+ describe ".new" do
3
+ subject { described_class.new(source) }
4
+
5
+ context "with a pointer to the external subpath" do
6
+ let(:source) { "https://example.com#/foo/ /e%5Ef/a~10b/m~01n" }
7
+
8
+ it { is_expected.to eq source }
9
+ its(:uri) { is_expected.to eq URI("https://example.com") }
10
+ its(:path) { is_expected.to eq ["foo", " ", "e^f", "a/0b", "m~1n"] }
11
+ end
12
+
13
+ context "with an explicit pointer to the current root" do
14
+ let(:source) { "#" }
15
+
16
+ it { is_expected.to eq source }
17
+ its(:uri) { is_expected.to be_nil }
18
+ its(:path) { is_expected.to eq [] }
19
+ end
20
+
21
+ context "with an implicit pointer to the current root" do
22
+ let(:source) { "" }
23
+
24
+ it { is_expected.to eq "#" }
25
+ its(:uri) { is_expected.to be_nil }
26
+ its(:path) { is_expected.to eq [] }
27
+ end
28
+
29
+ context "with a local pointer" do
30
+ let(:source) { "#/foo/ /e%5Ef/a~10b/m~01n" }
31
+
32
+ it { is_expected.to eq source }
33
+ its(:uri) { is_expected.to be_nil }
34
+ its(:path) { is_expected.to eq ["foo", " ", "e^f", "a/0b", "m~1n"] }
35
+ end
36
+
37
+ context "with an explicit pointer to the external root" do
38
+ let(:source) { "https://example.com/#" }
39
+
40
+ it { is_expected.to eq source }
41
+ its(:uri) { is_expected.to eq URI("https://example.com/") }
42
+ its(:path) { is_expected.to eq [] }
43
+ end
44
+
45
+ context "without an implicit pointer to the external root" do
46
+ let(:source) { "https://example.com/foo" }
47
+
48
+ it { is_expected.to eq "https://example.com/foo#" }
49
+ its(:uri) { is_expected.to eq URI("https://example.com/foo") }
50
+ its(:path) { is_expected.to eq [] }
51
+ end
52
+
53
+ context "with an external pointer" do
54
+ let(:source) { "https://example.com/foo#/foo/ /e%5Ef/a~10b/m~01n" }
55
+
56
+ it { is_expected.to eq source }
57
+ its(:uri) { is_expected.to eq URI("https://example.com/foo") }
58
+ its(:path) { is_expected.to eq ["foo", " ", "e^f", "a/0b", "m~1n"] }
59
+ end
60
+
61
+ context "with several #" do
62
+ let(:source) { "#/foo#/ /e%5Ef/a~10b/m~01n" }
63
+
64
+ it "raises ArgumentError" do
65
+ expect { subject }.to raise_error(ArgumentError)
66
+ end
67
+ end
68
+
69
+ context "with a # succeeded not by a slash" do
70
+ let(:source) { "# /foo/ /e%5Ef/a~10b/m~01n" }
71
+
72
+ it "raises ArgumentError" do
73
+ expect { subject }.to raise_error(ArgumentError)
74
+ end
75
+ end
76
+ end
77
+
78
+ describe ".[]" do
79
+ subject { described_class[*path] }
80
+
81
+ let(:path) { %w[foo~/bar 0 baz/~qux] }
82
+
83
+ it { is_expected.to eq "#/foo~0~1bar/0/baz~1~0qux" }
84
+ end
85
+
86
+ describe "#fetch_from" do
87
+ subject { ref.fetch_from(data) }
88
+
89
+ let(:ref) { described_class.new "#/foo/0/bar" }
90
+
91
+ context "when value is present" do
92
+ let(:data) { { "foo" => [{ "bar" => :BAZ }] } }
93
+
94
+ it "finds the value" do
95
+ expect(subject).to eq :BAZ
96
+ end
97
+ end
98
+
99
+ context "when value is absent" do
100
+ let(:data) { { "foo" => { "bar" => :BAZ } } }
101
+
102
+ it "raises key error" do
103
+ expect { subject }.to raise_error(KeyError, %r{'#/foo/0'})
104
+ end
105
+ end
106
+ end
107
+ end