operation 0.0.3 → 1.0.0.beta
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 +64 -28
- data/Rakefile +1 -0
- data/lib/operation/deferrable.rb +90 -0
- data/lib/operation/progress.rb +18 -0
- data/lib/operation/version.rb +4 -3
- data/lib/operation.rb +31 -8
- data/lib/operations.rb +2 -2
- data/spec/lib/operation/deferrable_spec.rb +69 -0
- data/spec/lib/operation_spec.rb +24 -2
- data/spec/spec_helper.rb +10 -0
- metadata +20 -17
- data/MIT-LICENSE +0 -20
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e609f4ffd6fbf2a50ff0667fbf4bfdedd3a2f874
|
4
|
+
data.tar.gz: 28a06955ebe29eba6317917ae0be8dd3211a4711
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8f8ee5b725295f6bd571e116775662374ae825f348752dd8284b244f45cb76cf160cbe50e16db8fbe1706bb3dd35e120caac1ad3da858492a0accae7473f835e
|
7
|
+
data.tar.gz: 6eb1d71f1794e0eebccb48f260922a0675de4f16f9d49f92e8cd15977f6bacd4b72390155bb114d7947a96df020f726e6807f4d66682b14f15e9c16c55942efe
|
data/README.md
CHANGED
@@ -4,7 +4,7 @@ Imagine you have a class like this:
|
|
4
4
|
|
5
5
|
```ruby
|
6
6
|
class User
|
7
|
-
def delete(id)
|
7
|
+
def self.delete(id)
|
8
8
|
@users.delete id
|
9
9
|
end
|
10
10
|
end
|
@@ -12,42 +12,32 @@ end
|
|
12
12
|
|
13
13
|
What does it return? True? The User? an ID? What if an error occured? How does anyone calling `User.delete 42` know what happened?
|
14
14
|
|
15
|
-
This is where `Operation` comes in. You would
|
15
|
+
This is where `Operation` comes in. You would simply return an operation object. That instance holds information about what happened, like so:
|
16
16
|
|
17
17
|
```ruby
|
18
18
|
class User
|
19
|
-
def delete(id)
|
19
|
+
def self.delete(id)
|
20
20
|
return Operation.new(code: :id_missing) unless id
|
21
21
|
return Operation.new(code: :invalid_id, id: id) unless id.match /[a-f]{8}/
|
22
22
|
|
23
23
|
user = @users[id]
|
24
24
|
if @users.delete id
|
25
|
-
Operation.new success:true, code: :user_deleted, user: user
|
25
|
+
Operation.new success: true, code: :user_deleted, user: user
|
26
26
|
else
|
27
|
-
Operation.new code: :deletion_failed
|
27
|
+
Operation.new code: :deletion_failed, id: id
|
28
28
|
end
|
29
|
+
|
29
30
|
rescue ConnectionError
|
30
|
-
Operation.new code: :deletion_failed_badly
|
31
|
+
Operation.new code: :deletion_failed_badly, id: id
|
31
32
|
end
|
32
33
|
end
|
33
34
|
```
|
34
35
|
|
35
|
-
|
36
|
-
|
37
|
-
```ruby
|
38
|
-
operation = User.delete 42
|
39
|
-
if operation.success?
|
40
|
-
puts "It worked! You deleted the user #{operation.meta.user.first_name}"
|
41
|
-
else
|
42
|
-
puts "Oh, could not delete User with ID #{operation.object} because #{operation.code}"
|
43
|
-
end
|
44
|
-
```
|
45
|
-
|
46
|
-
For your convenience you can use `Operations#failure` and `Operations.success`:
|
36
|
+
There are some shortcuts too keep it less verbose:
|
47
37
|
|
48
38
|
```ruby
|
49
39
|
class User
|
50
|
-
def delete(id)
|
40
|
+
def self.delete(id)
|
51
41
|
return Operations.failure(:id_missing) unless id
|
52
42
|
return Operations.failure(:invalid_id, id: id) unless id.match /[a-f]{8}/
|
53
43
|
|
@@ -57,27 +47,73 @@ class User
|
|
57
47
|
else
|
58
48
|
Operations.failure :deletion_failed
|
59
49
|
end
|
50
|
+
|
60
51
|
rescue ConnectionError
|
61
52
|
Operation.failure :deletion_failed_badly
|
62
53
|
end
|
63
54
|
end
|
64
55
|
```
|
65
56
|
|
66
|
-
|
57
|
+
### So what are the benefits?
|
58
|
+
|
59
|
+
#### 1. Robust and predictable code
|
60
|
+
|
61
|
+
This will give you this joyful, consistent, conventional, implementation-unaware programming feeling:
|
62
|
+
|
63
|
+
```ruby
|
64
|
+
operation = User.delete 42
|
65
|
+
|
66
|
+
if operation.success?
|
67
|
+
puts "It worked! You deleted the user #{operation.meta.user.first_name}"
|
68
|
+
else
|
69
|
+
puts "Aw, could not delete User with ID #{operation.meta.id} because #{operation.code}"
|
70
|
+
end
|
71
|
+
```
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
operation = User.delete 42
|
75
|
+
|
76
|
+
operation.success? # => true
|
77
|
+
operation.failure? # => false
|
78
|
+
operation.metadata # => { object: <#User id=42> }
|
79
|
+
operation.meta # => { object: <#User id=42> }
|
80
|
+
operation.object # => <#User id=42> <- shortcut for meta[:object]
|
81
|
+
|
82
|
+
# In case you use Hashie, you will get that via #meta
|
83
|
+
require 'hashie/mash'
|
84
|
+
operation.meta # => <#Hashie::Mash object: <#User id=42>>
|
85
|
+
operation.object # => <#User id=42> <- shortcut for meta.object
|
86
|
+
```
|
87
|
+
|
88
|
+
#### 2. Better tests
|
89
|
+
|
90
|
+
How would you test this code?
|
67
91
|
|
68
92
|
```ruby
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
93
|
+
class Product
|
94
|
+
def self.delete(id)
|
95
|
+
return false if id.blank?
|
96
|
+
return false unless product = Products.find(id)
|
97
|
+
return false unless permission?
|
98
|
+
api.update(id, attributes)
|
99
|
+
end
|
100
|
+
|
101
|
+
def self.permission?
|
102
|
+
Date.today.sunday?
|
103
|
+
end
|
104
|
+
end
|
75
105
|
```
|
76
106
|
|
107
|
+
You cannot simply test for the `false` as expected return value because it could mean anything.
|
108
|
+
|
109
|
+
#### 3. Documentation
|
110
|
+
|
111
|
+
While the code becomes more verbose, that verbosity translates directly into documenation. You see immediately what each line is doing.
|
112
|
+
|
77
113
|
### Requirements
|
78
114
|
|
79
|
-
* Ruby >= 1
|
115
|
+
* Ruby >= 2.1
|
80
116
|
|
81
117
|
### Copyright
|
82
118
|
|
83
|
-
MIT
|
119
|
+
MIT 2015 halo. See [MIT-LICENSE](http://github.com/halo/operation/blob/master/LICENSE.md).
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
@@ -0,0 +1,90 @@
|
|
1
|
+
class Operation
|
2
|
+
module Deferrable
|
3
|
+
|
4
|
+
attr_reader :arguments
|
5
|
+
|
6
|
+
def on_progress(&block)
|
7
|
+
return unless block
|
8
|
+
progressables.unshift block
|
9
|
+
end
|
10
|
+
|
11
|
+
def on_success(&block)
|
12
|
+
return unless block
|
13
|
+
|
14
|
+
if succeeded?
|
15
|
+
block.call(*arguments)
|
16
|
+
elsif !failed?
|
17
|
+
successables.unshift block
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def on_failure(&block)
|
22
|
+
return unless block
|
23
|
+
|
24
|
+
if failed?
|
25
|
+
block.call(*arguments)
|
26
|
+
elsif !succeeded?
|
27
|
+
failables.unshift block
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def succeeded?
|
32
|
+
status == :succeeded
|
33
|
+
end
|
34
|
+
|
35
|
+
def failed?
|
36
|
+
status == :failed
|
37
|
+
end
|
38
|
+
|
39
|
+
def status
|
40
|
+
@status ||= :unknown
|
41
|
+
end
|
42
|
+
|
43
|
+
def set_status(new_status, *args)
|
44
|
+
@arguments = args
|
45
|
+
@status = new_status
|
46
|
+
|
47
|
+
if succeeded? && !successables.empty?
|
48
|
+
while callback = successables.pop
|
49
|
+
callback.call(*arguments)
|
50
|
+
end
|
51
|
+
failables.clear if !failables.empty?
|
52
|
+
|
53
|
+
elsif failed? && !failables.empty?
|
54
|
+
while callback = failables.pop
|
55
|
+
callback.call(*arguments)
|
56
|
+
end
|
57
|
+
successables.clear if !failables.empty?
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def progress(percent, code)
|
62
|
+
return if succeeded? || failed?
|
63
|
+
|
64
|
+
progressables.each do |callback|
|
65
|
+
callback.call ::Operation::Progress.new(percent, code)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def succeed(*args)
|
70
|
+
set_status :succeeded, *args
|
71
|
+
end
|
72
|
+
|
73
|
+
def fail(*args)
|
74
|
+
set_status :failed, *args
|
75
|
+
end
|
76
|
+
|
77
|
+
def successables
|
78
|
+
@successables ||= []
|
79
|
+
end
|
80
|
+
|
81
|
+
def failables
|
82
|
+
@failables ||= []
|
83
|
+
end
|
84
|
+
|
85
|
+
def progressables
|
86
|
+
@progressables ||= []
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
end
|
data/lib/operation/version.rb
CHANGED
data/lib/operation.rb
CHANGED
@@ -1,30 +1,48 @@
|
|
1
|
-
require '
|
2
|
-
require '
|
1
|
+
require 'operation/deferrable'
|
2
|
+
require 'operation/progress'
|
3
|
+
require 'operations'
|
3
4
|
|
4
5
|
class Operation
|
5
|
-
|
6
|
+
include ::Operation::Deferrable
|
6
7
|
attr_reader :metadata, :code
|
7
8
|
|
8
9
|
def initialize(options = {})
|
9
|
-
@
|
10
|
-
@code = options[:code].to_s.to_sym unless options[:code].to_s == ''
|
11
|
-
@metadata = options[:metadata]
|
10
|
+
@options = options
|
12
11
|
end
|
13
12
|
|
14
13
|
def success?
|
15
|
-
|
14
|
+
[true, 'true', 1, '1'].include? options[:success]
|
16
15
|
end
|
17
16
|
|
18
17
|
def failure?
|
19
18
|
!success?
|
20
19
|
end
|
21
20
|
|
21
|
+
def code
|
22
|
+
return if options[:code].to_s == ''
|
23
|
+
options[:code].to_s.to_sym
|
24
|
+
end
|
25
|
+
|
22
26
|
# Convenience Wrapper
|
23
27
|
def object
|
24
|
-
meta.object
|
28
|
+
meta[:object] || meta['object'] || meta.object
|
29
|
+
rescue
|
30
|
+
nil
|
31
|
+
end
|
32
|
+
|
33
|
+
def metadata
|
34
|
+
options[:metadata]
|
25
35
|
end
|
26
36
|
|
27
37
|
def meta
|
38
|
+
if defined? Hashie::Mash
|
39
|
+
metamash
|
40
|
+
else
|
41
|
+
metadata
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def metamash
|
28
46
|
if metadata.respond_to? :each_pair
|
29
47
|
Hashie::Mash.new metadata
|
30
48
|
elsif metadata
|
@@ -33,4 +51,9 @@ class Operation
|
|
33
51
|
Hashie::Mash.new
|
34
52
|
end
|
35
53
|
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
attr_reader :options
|
58
|
+
|
36
59
|
end
|
data/lib/operations.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
module Operations
|
2
2
|
|
3
3
|
def self.success(code = :success, metadata = {})
|
4
|
-
Operation.new success: true, code: code, metadata: metadata
|
4
|
+
::Operation.new success: true, code: code, metadata: metadata
|
5
5
|
end
|
6
6
|
|
7
7
|
def self.failure(code, metadata = {})
|
8
|
-
Operation.new success: false, code: code, metadata: metadata
|
8
|
+
::Operation.new success: false, code: code, metadata: metadata
|
9
9
|
end
|
10
10
|
|
11
11
|
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe Operation do
|
4
|
+
let(:operation) { Operation.new }
|
5
|
+
let(:successables) { [] }
|
6
|
+
let(:progressables) { [] }
|
7
|
+
let(:failables) { [] }
|
8
|
+
|
9
|
+
describe '#on_progress' do
|
10
|
+
before do
|
11
|
+
operation.on_progress { |result| progressables << result }
|
12
|
+
operation.progress 42, :still_working
|
13
|
+
operation.progress 99, :almost_done
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'calls all the successables' do
|
17
|
+
expect(progressables.size).to eq 2
|
18
|
+
expect(progressables.first.percent).to eq 42
|
19
|
+
expect(progressables.first.code).to eq :still_working
|
20
|
+
expect(progressables.last.percent).to eq 99
|
21
|
+
expect(progressables.last.code).to eq :almost_done
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'does not call the successables' do
|
25
|
+
expect(successables).to be_empty
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'does not call the failables' do
|
29
|
+
expect(failables).to be_empty
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'does not alter the state' do
|
33
|
+
expect(operation.status).to eq :unknown
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe '#on_success' do
|
38
|
+
before do
|
39
|
+
operation.on_success { successables << 'called' }
|
40
|
+
operation.on_success { |result| successables << result }
|
41
|
+
operation.succeed :great
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'calls all the successables' do
|
45
|
+
expect(successables).to eq ['called', :great]
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'does not call the failables' do
|
49
|
+
expect(failables).to be_empty
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe '#on_failure' do
|
54
|
+
before do
|
55
|
+
operation.on_failure { failables << 'called' }
|
56
|
+
operation.on_failure { |result| failables << result }
|
57
|
+
operation.fail :bummer
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'calls all the successables' do
|
61
|
+
expect(failables).to eq ['called', :bummer]
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'does not call the failables' do
|
65
|
+
expect(successables).to be_empty
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
data/spec/lib/operation_spec.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
-
require 'operation'
|
3
2
|
|
4
|
-
describe Operation do
|
3
|
+
RSpec.describe Operation do
|
5
4
|
|
6
5
|
describe '#success' do
|
7
6
|
it 'handles different types of success' do
|
@@ -18,6 +17,7 @@ describe Operation do
|
|
18
17
|
expect( Operation.new(success: 'false') ).to be_failure
|
19
18
|
expect( Operation.new(success: 0) ).to be_failure
|
20
19
|
expect( Operation.new(success: '0') ).to be_failure
|
20
|
+
expect( Operation.new ).to be_failure
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
@@ -45,7 +45,29 @@ describe Operation do
|
|
45
45
|
end
|
46
46
|
end
|
47
47
|
|
48
|
+
describe 'test setup' do
|
49
|
+
it 'has no Hashie' do
|
50
|
+
expect { Hashie }.to raise_error NameError
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
48
54
|
describe '#meta' do
|
55
|
+
it 'is nil for nil' do
|
56
|
+
expect( Operation.new.meta ).to be_nil
|
57
|
+
expect( Operation.new(metadata: nil).meta ).to be_nil
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'is whatever was passed in' do
|
61
|
+
expect( Operation.new(metadata: {}).meta ).to be_instance_of Hash
|
62
|
+
expect( Operation.new(metadata: { one: { two: :three } }).meta[:one][:two] ).to eq :three
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe '#meta', :hashie do
|
67
|
+
before :all do
|
68
|
+
require 'hashie/mash'
|
69
|
+
end
|
70
|
+
|
49
71
|
it 'is a Mash for nil' do
|
50
72
|
expect( Operation.new.meta ).to be_instance_of Hashie::Mash
|
51
73
|
expect( Operation.new(metadata: nil).meta ).to be_instance_of Hashie::Mash
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,69 +1,69 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: operation
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 1.0.0.beta
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- halo
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2015-03-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: hashie
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- -
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '0'
|
20
|
-
type: :
|
20
|
+
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- -
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rspec
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- -
|
31
|
+
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: '0'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- -
|
38
|
+
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: guard-rspec
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- -
|
45
|
+
- - ">="
|
46
46
|
- !ruby/object:Gem::Version
|
47
47
|
version: '0'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- -
|
52
|
+
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: rb-fsevent
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
|
-
- -
|
59
|
+
- - ">="
|
60
60
|
- !ruby/object:Gem::Version
|
61
61
|
version: '0'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
|
-
- -
|
66
|
+
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0'
|
69
69
|
description: Implementation-agnostic method return status objects (say what?). Your
|
@@ -74,11 +74,14 @@ executables: []
|
|
74
74
|
extensions: []
|
75
75
|
extra_rdoc_files: []
|
76
76
|
files:
|
77
|
-
- MIT-LICENSE
|
78
77
|
- README.md
|
78
|
+
- Rakefile
|
79
79
|
- lib/operation.rb
|
80
|
+
- lib/operation/deferrable.rb
|
81
|
+
- lib/operation/progress.rb
|
80
82
|
- lib/operation/version.rb
|
81
83
|
- lib/operations.rb
|
84
|
+
- spec/lib/operation/deferrable_spec.rb
|
82
85
|
- spec/lib/operation_spec.rb
|
83
86
|
- spec/spec_helper.rb
|
84
87
|
homepage: https://github.com/halo/operation
|
@@ -91,17 +94,17 @@ require_paths:
|
|
91
94
|
- lib
|
92
95
|
required_ruby_version: !ruby/object:Gem::Requirement
|
93
96
|
requirements:
|
94
|
-
- -
|
97
|
+
- - ">="
|
95
98
|
- !ruby/object:Gem::Version
|
96
|
-
version: 1.
|
99
|
+
version: 2.1.0
|
97
100
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
98
101
|
requirements:
|
99
|
-
- -
|
102
|
+
- - ">"
|
100
103
|
- !ruby/object:Gem::Version
|
101
|
-
version:
|
104
|
+
version: 1.3.1
|
102
105
|
requirements: []
|
103
106
|
rubyforge_project:
|
104
|
-
rubygems_version: 2.
|
107
|
+
rubygems_version: 2.4.5
|
105
108
|
signing_key:
|
106
109
|
specification_version: 4
|
107
110
|
summary: Implementation-agnostic method return status objects
|
data/MIT-LICENSE
DELETED
@@ -1,20 +0,0 @@
|
|
1
|
-
Copyright 2014 halo
|
2
|
-
|
3
|
-
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
-
a copy of this software and associated documentation files (the
|
5
|
-
"Software"), to deal in the Software without restriction, including
|
6
|
-
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
-
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
-
permit persons to whom the Software is furnished to do so, subject to
|
9
|
-
the following conditions:
|
10
|
-
|
11
|
-
The above copyright notice and this permission notice shall be
|
12
|
-
included in all copies or substantial portions of the Software.
|
13
|
-
|
14
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
-
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
-
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
-
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
-
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
-
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
-
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|