Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
beauby committed Mar 10, 2016
0 parents commit c0ea333
Show file tree
Hide file tree
Showing 21 changed files with 706 additions and 0 deletions.
36 changes: 36 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
*.gem
*.rbc
/.config
/coverage/
/InstalledFiles
/pkg/
/spec/reports/
/spec/examples.txt
/test/tmp/
/test/version_tmp/
/tmp/

## Specific to RubyMotion:
.dat*
.repl_history
build/

## Documentation cache and generated files:
/.yardoc/
/_yardoc/
/doc/
/rdoc/

## Environment normalization:
/.bundle/
/vendor/bundle
/lib/bundler/man/

# for a library or gem, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# Gemfile.lock
# .ruby-version
# .ruby-gemset

# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
.rvmrc
3 changes: 3 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
source 'https://rubygems.org'

gemspec
32 changes: 32 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
PATH
remote: .
specs:
jsonapi_parser (0.1.0)

GEM
remote: https://rubygems.org/
specs:
diff-lcs (1.2.5)
rspec (3.4.0)
rspec-core (~> 3.4.0)
rspec-expectations (~> 3.4.0)
rspec-mocks (~> 3.4.0)
rspec-core (3.4.4)
rspec-support (~> 3.4.0)
rspec-expectations (3.4.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.4.0)
rspec-mocks (3.4.1)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.4.0)
rspec-support (3.4.1)

PLATFORMS
ruby

DEPENDENCIES
jsonapi_parser!
rspec (~> 3.4)

BUNDLED WITH
1.10.6
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
The MIT License (MIT)

Copyright (c) 2016 Lucas Hosseini

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
134 changes: 134 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
# JsonApiParser
Parse and validate [JSON API](http://jsonapi.org) documents.

## Installation

Add the following to your application's Gemfile:
```ruby
gem 'jsonapi_parser', '~>0.1'
```
And then execute:
```
$ bundle
```
Or install it manually as:
```
$ gem install jsonapi_parser
```

## Usage

First, `require` the gem.
```ruby
require 'jsonapi_parser'
```

Then, parse a hash representing a JSON API document:
```ruby
document = JsonApiParser.parse(json_hash)
```

In order to parse a JSON string, one can use `JSON.parse`:
```ruby
json_hash = JSON.parse(json_string)
```

## Examples

```ruby
document_hash = JSON.parse(json_document)
document = JsonApiParser.parse(document_hash)
# Should the document be invalid, the parse method would fail with an
# InvalidDocument error.

document.data.link_defined?(:self)
# => true
document.data.links.self.value
# => 'http://example.com/articles/1'
document.data.attributes_keys
# => ['title']
document.data.attribute_defined?(:title)
# => true
document.data.attributes.title
# => 'JSON API paints my bikeshed!'
document.data.relationships_keys
# => ['author', 'comments']
document.data.relationship_defined?(:author)
# => true
document.data.relationships.author.collection?
# => false
document.data.relationships.author.data.id
# => 9
document.data.relationships.author.data.type
# => 'people'
document.data.relationships.author.link_defined?(:self)
# => true
document.data.relationships.author.links.self.value
# => 'http://example.com/articles/1/relationships/author'
document.data.relationship_defined?(:comments)
# => true
document.data.relationships.comments.collection?
# => true
document.data.relationships.comments.data.size
# => 2
document.data.relationships.comments.data[0].id
# => 5
document.data.relationships.comments.data[0].type
# => 'comments'
document.data.relationships.comments.link_defined?(:self)
# => true
document.data.relationships.comments.links.self.value
# => 'http://example.com/articles/1/relationships/comments'

# for the following document_hash
document_hash = {
'data' =>
{
'type' => 'articles',
'id' => '1',
'attributes' => {
'title' => 'JSON API paints my bikeshed!'
},
'links' => {
'self' => 'http://example.com/articles/1'
},
'relationships' => {
'author' => {
'links' => {
'self' => 'http://example.com/articles/1/relationships/author',
'related' => 'http://example.com/articles/1/author'
},
'data' => { 'type' => 'people', 'id' => '9' }
},
'comments' => {
'links' => {
'self' => 'http://example.com/articles/1/relationships/comments',
'related' => 'http://example.com/articles/1/comments'
},
'data' => [
{ 'type' => 'comments', 'id' => '5' },
{ 'type' => 'comments', 'id' => '12' }
]
}
}
}
}
```

## Contributing

1. Fork the [official repository](https://github.com/beauby/jsonapi_parser/tree/master).
2. Make your changes in a topic branch.
3. Send a pull request.

Notes:

* Contributions without tests won't be accepted.
* Please don't update the Gem version.

## License

json_matchers is Copyright © 2016 Lucas Hosseini.

It is free software, and may be redistributed under the terms specified in the
[LICENSE](LICENSE.md) file.
21 changes: 21 additions & 0 deletions jsonapi_parser.gemspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
lib = File.expand_path('../lib', __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'jsonapi_parser/version'

Gem::Specification.new do |spec|
spec.name = 'jsonapi_parser'
spec.version = JsonApiParser::VERSION
spec.authors = ['Lucas Hosseini']
spec.email = ['lucas.hosseini@gmail.com']
spec.summary = 'Parse JSON API documents'
spec.description = 'Parser for JSON API documents'
spec.homepage = 'https://github.com/beauby/jsonapi_parser'
spec.license = 'MIT'

spec.files = `git ls-files -z`.split("\x0")
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
spec.test_files = spec.files.grep(%r{^(spec)/})
spec.require_paths = ['lib']

spec.add_development_dependency 'rspec', '~>3.4'
end
22 changes: 22 additions & 0 deletions lib/jsonapi_parser.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
require 'jsonapi_parser/linkable'
require 'jsonapi_parser/exceptions'

require 'jsonapi_parser/attributes'
require 'jsonapi_parser/document'
require 'jsonapi_parser/error'
require 'jsonapi_parser/jsonapi'
require 'jsonapi_parser/link'
require 'jsonapi_parser/links'
require 'jsonapi_parser/relationship'
require 'jsonapi_parser/relationships'
require 'jsonapi_parser/resource'
require 'jsonapi_parser/resource_identifier'
require 'jsonapi_parser/version'

module JsonApiParser
module_function

def parse(hash, options = {})
Document.new(hash, options)
end
end
15 changes: 15 additions & 0 deletions lib/jsonapi_parser/attributes.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
module JsonApiParser
# c.f. http://jsonapi.org/format/#document-resource-object-attributes
class Attributes
def initialize(attributes_hash, options = {})
fail InvalidDocument,
"the value of 'attributes' MUST be an object" unless
attributes_hash.is_a?(Hash)
@attributes = attributes_hash
@attributes.each do |attr_name, attr_val|
instance_variable_set("@#{attr_name}", attr_val)
singleton_class.class_eval { attr_reader attr_name }
end
end
end
end
91 changes: 91 additions & 0 deletions lib/jsonapi_parser/document.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
module JsonApiParser
# c.f. http://jsonapi.org/format/#document-top-level
class Document
include Linkable

attr_reader :data, :meta, :errors, :json_api, :links, :included

def initialize(document_hash, options = {})
@options = options
@data = parse_data(document_hash['data']) if document_hash.key?('data')
@meta = document_hash['meta'] if document_hash.key?('meta')
@errors = parse_errors(document_hash['errors']) if
document_hash.key?('errors')
@jsonapi = JsonApi.new(document_hash['jsonapi'], @options) if
document_hash.key?('jsonapi')
@links_hash = document_hash['links'] || {}
@links = Links.new(@links_hash, @options)
@included = parse_included(document_hash['included']) if
document_hash.key?('included')

validate!
end

def collection?
@data.is_a?(Array)
end

private

def validate!
case
when !@data && !@meta && !@errors
fail InvalidDocument,
"a document MUST contain at least one of 'data', 'meta', or" \
" or 'errors' at top-level"
when @data && @errors
fail InvalidDocument,
"'data' and 'errors' MUST NOT coexist in the same document"
when !@data && @included
fail InvalidDocument, "'included' MUST NOT be present unless 'data' is"
when @options[:verify_duplicates] && duplicates?
fail InvalidDocument,
"resources MUST NOT appear both in 'data' and 'included'"
when @options[:verify_linkage] && !full_linkage?
fail InvalidDocument,
"resources in 'included' MUST respect full-linkage"
end
end

def duplicates?
return true unless @included

# TODO
false
end

def full_linkage?
return true unless @included

# TODO
true
end

def parse_data(data_hash)
collection = data_hash.is_a?(Array)
if collection
data_hash.map { |h| Resource.new(h, @options.merge(id_optional: true)) }
elsif data_hash.nil?
nil
else
Resource.new(data_hash, @options.merge(id_optional: true))
end
end

def parse_included(included_hash)
fail InvalidDocument,
"the value of 'included' MUST be an array of resource objects" unless
included_hash.is_a?(Array)

included_hash.map { |h| Resource.new(h, @options) }
end

def parse_errors(errors_hash)
fails InvalidDocument,
"the value of 'errors' MUST be an array of error objects" unless
errors_hash.is_a?(Array)

errors_hash.map { |h| Error.new(h, @options) }
end
end
end
Loading

0 comments on commit c0ea333

Please sign in to comment.