icss 0.1.3 → 0.3.2
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.
- data/.watchr +35 -3
- data/CHANGELOG.md +38 -0
- data/Gemfile +19 -14
- data/README.md +296 -0
- data/Rakefile +2 -6
- data/TODO.md +13 -0
- data/VERSION +1 -1
- data/examples/avro_examples/complicated.icss.yaml +14 -13
- data/examples/bnc.icss.yaml +70 -0
- data/examples/chronic.icss.yaml +3 -3
- data/examples/license.icss.yaml +7 -0
- data/examples/source1.icss.yaml +4 -0
- data/examples/source2.icss.yaml +4 -0
- data/examples/test_icss.yaml +67 -0
- data/icss.gemspec +103 -43
- data/lib/icss.rb +37 -15
- data/lib/icss/core_types.rb +19 -0
- data/lib/icss/error.rb +4 -0
- data/{init.rb → lib/icss/init.rb} +0 -0
- data/lib/icss/message.rb +124 -66
- data/lib/icss/message/message_sample.rb +144 -0
- data/lib/icss/protocol.rb +184 -131
- data/lib/icss/protocol/code_asset.rb +18 -0
- data/lib/icss/protocol/data_asset.rb +23 -0
- data/lib/icss/protocol/license.rb +41 -0
- data/lib/icss/protocol/source.rb +37 -0
- data/lib/icss/protocol/target.rb +68 -0
- data/lib/icss/receiver_model.rb +24 -0
- data/lib/icss/receiver_model/active_model_shim.rb +36 -0
- data/lib/icss/receiver_model/acts_as_catalog.rb +170 -0
- data/lib/icss/receiver_model/acts_as_hash.rb +177 -0
- data/lib/icss/receiver_model/acts_as_loadable.rb +47 -0
- data/lib/icss/receiver_model/acts_as_tuple.rb +100 -0
- data/lib/icss/receiver_model/locale/en.yml +27 -0
- data/lib/icss/receiver_model/to_geo_json.rb +19 -0
- data/lib/icss/receiver_model/tree_merge.rb +34 -0
- data/lib/icss/receiver_model/validations.rb +31 -0
- data/lib/icss/serialization.rb +51 -0
- data/lib/icss/serialization/zaml.rb +443 -0
- data/lib/icss/type.rb +148 -501
- data/lib/icss/type/base_type.rb +0 -0
- data/lib/icss/type/named_type.rb +184 -0
- data/lib/icss/type/record_field.rb +77 -0
- data/lib/icss/type/record_model.rb +49 -0
- data/lib/icss/type/record_schema.rb +54 -0
- data/lib/icss/type/record_type.rb +325 -0
- data/lib/icss/type/simple_types.rb +72 -0
- data/lib/icss/type/structured_schema.rb +288 -0
- data/lib/icss/type/type_factory.rb +144 -0
- data/lib/icss/type/union_schema.rb +41 -0
- data/lib/icss/view_helper.rb +56 -19
- data/notes/named_array.md +32 -0
- data/notes/on_include_vs_extend_etc.rb +176 -0
- data/notes/technical_details.md +278 -0
- data/spec/core_types_spec.rb +119 -0
- data/spec/fixtures/zaml_complex_hash.yaml +35 -0
- data/spec/icss_spec.rb +86 -23
- data/spec/message/message_sample_spec.rb +4 -0
- data/spec/message_spec.rb +139 -0
- data/spec/protocol/license_spec.rb +67 -0
- data/spec/protocol/protocol_catalog_spec.rb +48 -0
- data/spec/protocol/protocol_validations_spec.rb +176 -0
- data/spec/protocol/source_spec.rb +65 -0
- data/spec/protocol_spec.rb +91 -37
- data/spec/receiver_model_spec.rb +111 -0
- data/spec/serialization/zaml_spec.rb +81 -0
- data/spec/serialization/zaml_test.rb +473 -0
- data/spec/serialization_spec.rb +63 -0
- data/spec/spec_helper.rb +24 -7
- data/spec/support/icss_test_helper.rb +67 -0
- data/spec/support/load_example_protocols.rb +17 -0
- data/spec/type/base_type_spec.rb +0 -0
- data/spec/type/named_type_spec.rb +75 -0
- data/spec/type/record_field_spec.rb +44 -0
- data/spec/type/record_model_spec.rb +206 -0
- data/spec/type/record_schema_spec.rb +161 -0
- data/spec/type/record_type_spec.rb +155 -0
- data/spec/type/simple_types_spec.rb +121 -0
- data/spec/type/structured_schema_spec.rb +300 -0
- data/spec/type/type_catalog_spec.rb +44 -0
- data/spec/type/type_factory_spec.rb +93 -0
- data/spec/type/union_schema_spec.rb +0 -0
- data/spec/type_spec.rb +63 -0
- metadata +205 -144
- data/CHANGELOG.textile +0 -9
- data/Gemfile.lock +0 -40
- data/README.textile +0 -29
- data/lib/icss/brevity.rb +0 -136
- data/lib/icss/code_asset.rb +0 -16
- data/lib/icss/core_ext.rb +0 -9
- data/lib/icss/data_asset.rb +0 -22
- data/lib/icss/old.rb +0 -96
- data/lib/icss/protocol_set.rb +0 -48
- data/lib/icss/sample_message_call.rb +0 -142
- data/lib/icss/target.rb +0 -72
- data/lib/icss/type/factory.rb +0 -196
- data/lib/icss/validations.rb +0 -16
- data/spec/validations_spec.rb +0 -171
@@ -0,0 +1,19 @@
|
|
1
|
+
# p [__FILE__, "core types from ", Settings.catalog_root]
|
2
|
+
|
3
|
+
module Icss
|
4
|
+
module Encyclopedic ; module Wikipedia ; module Dbpedia ; end ; end ; end
|
5
|
+
module Web ; module An ; end ; end
|
6
|
+
module Engineering ; module Chemical; module Msds ; end ; end ; end
|
7
|
+
module Government ; module Public ; module Acs ; end ; end ; end
|
8
|
+
module Language ; module Corpora ; module WordFreq ; end ; end ; end
|
9
|
+
module Sports ; module Stats ; module Baseball ; end ; module Vargatron ; end ; end ; end
|
10
|
+
module Social ; module Network ; module Tw ; end ; module Qwerly ; end ; end ; end
|
11
|
+
module Soc ; module Net ; module Tw ; end ; end ; end
|
12
|
+
module Meta ; module Req ; class Geolocator ; end ; end ; end
|
13
|
+
module St ; class Url < String ; end ; end
|
14
|
+
module Geo ; module Location ; end ; end
|
15
|
+
end
|
16
|
+
|
17
|
+
unless defined?(Icss::Geo::Place)
|
18
|
+
warn "Could not load core type 'place'. Make sure the catalog (#{Settings.catalog_root}) is where you expect it to be."
|
19
|
+
end
|
data/lib/icss/error.rb
ADDED
File without changes
|
data/lib/icss/message.rb
CHANGED
@@ -1,75 +1,133 @@
|
|
1
1
|
module Icss
|
2
|
+
module Meta
|
3
|
+
#
|
4
|
+
# Describes an Avro Message
|
5
|
+
#
|
6
|
+
# A message has attributes:
|
7
|
+
#
|
8
|
+
# * doc: an optional description of the message,
|
9
|
+
# * request: a list of named, typed parameter schemas (this has the same form as the fields of a record declaration);
|
10
|
+
# * response: a valid schema for the response
|
11
|
+
# * errors: an optional union of error schemas.
|
12
|
+
#
|
13
|
+
# A request parameter list is processed equivalently to an anonymous
|
14
|
+
# record. Since record field lists may vary between reader and writer, request
|
15
|
+
# parameters may also differ between the caller and responder, and such
|
16
|
+
# differences are resolved in the same manner as record field differences.
|
17
|
+
#
|
18
|
+
class Message
|
19
|
+
include ::Icss::ReceiverModel
|
2
20
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
#
|
8
|
-
# * doc: an optional description of the message,
|
9
|
-
# * request: a list of named, typed parameter schemas (this has the same form as the fields of a record declaration);
|
10
|
-
# * response: a valid schema for the response
|
11
|
-
# * errors: an optional union of error schemas.
|
12
|
-
#
|
13
|
-
# A request parameter list is processed equivalently to an anonymous
|
14
|
-
# record. Since record field lists may vary between reader and writer, request
|
15
|
-
# parameters may also differ between the caller and responder, and such
|
16
|
-
# differences are resolved in the same manner as record field differences.
|
17
|
-
#
|
18
|
-
class Message
|
19
|
-
include Receiver
|
20
|
-
include Receiver::ActsAsHash
|
21
|
-
rcvr_accessor :name, String
|
22
|
-
rcvr_accessor :doc, String
|
23
|
-
|
24
|
-
#we're starting to attach a lot of pork to this lib...
|
25
|
-
rcvr_accessor :initial_free_qty, Integer
|
26
|
-
rcvr_accessor :price_per_k_in_cents, Integer
|
27
|
-
|
28
|
-
rcvr_accessor :request, Array, :of => Icss::RecordField, :default => []
|
29
|
-
rcvr_accessor :response, Icss::TypeFactory
|
30
|
-
rcvr_accessor :errors, Icss::UnionType, :default => []
|
31
|
-
attr_accessor :protocol
|
32
|
-
# this is defined in sample_message_call.rb -- since we don't do referenced types yet
|
33
|
-
# rcvr_accessor :samples, Array, :of => Icss::SampleMessageCall, :default => []
|
34
|
-
|
35
|
-
after_receive do |hsh|
|
36
|
-
# track recursion of type references
|
37
|
-
@response_is_reference = true if hsh['response'].is_a?(String) || hsh['response'].is_a?(Symbol)
|
38
|
-
# tie each sample back to this, its parent message
|
39
|
-
(self.samples ||= []).each{|sample| sample.message = self }
|
40
|
-
end
|
21
|
+
field :name, String
|
22
|
+
alias_method :basename, :name
|
23
|
+
alias_method :basename=, :name=
|
24
|
+
field :doc, String
|
41
25
|
|
42
|
-
|
43
|
-
File.join(protocol.path, name)
|
44
|
-
end
|
26
|
+
field :request_decorators, Hash, :default => {:anchors => []}
|
45
27
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
28
|
+
field :request, Array, :items => Icss::Meta::RecordField, :default => []
|
29
|
+
field :response, Icss::Meta::TypeFactory
|
30
|
+
field :errors, Object # FIXME: Icss::Meta::UnionType, :default => []
|
31
|
+
# this is defined in sample_message_call.rb -- since we don't do referenced types yet
|
50
32
|
|
51
|
-
|
52
|
-
# Conversion
|
53
|
-
#
|
54
|
-
def to_hash()
|
55
|
-
{
|
56
|
-
:doc => doc,
|
57
|
-
:initial_free_qty => initial_free_qty,
|
58
|
-
:price_per_k_in_cents => price_per_k_in_cents,
|
59
|
-
:request => summary_of_request_attr,
|
60
|
-
:response => summary_of_response_attr,
|
61
|
-
:samples => samples.map(&:to_hash).map(&:compact_blank),
|
62
|
-
:errors => (errors.blank? ? nil : errors),
|
63
|
-
}.reject{|k,v| v.nil? }
|
64
|
-
end
|
65
|
-
def to_json(*args) to_hash.to_json(*args) ; end
|
33
|
+
attr_accessor :protocol
|
66
34
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
35
|
+
#we're starting to attach a lot of pork to this lib...
|
36
|
+
field :initial_free_qty, Integer
|
37
|
+
field :price_per_k_in_cents, Integer
|
38
|
+
|
39
|
+
after_receive(:are_my_types_references) do |hsh|
|
40
|
+
# track recursion of type references
|
41
|
+
@response_referenceness = ! hsh[:response].respond_to?(:each_pair)
|
42
|
+
end
|
43
|
+
|
44
|
+
after_receive(:parent_my_samples) do |hsh|
|
45
|
+
# # tie each sample back to this, its parent message
|
46
|
+
(self.samples||=[]).each{|samp| samp.message = self }
|
47
|
+
end
|
48
|
+
|
49
|
+
def fullname
|
50
|
+
"#{protocol.fullname}.#{basename}"
|
51
|
+
end
|
52
|
+
def path
|
53
|
+
fullname.gsub(%r{\.},'/')
|
54
|
+
end
|
55
|
+
|
56
|
+
# the type of the message's params (by convention, its first request field)
|
57
|
+
def params_type
|
58
|
+
request.first ? request.first.type : {}
|
59
|
+
end
|
60
|
+
|
61
|
+
def first_sample_request_param
|
62
|
+
req = samples.first.request.first rescue nil
|
63
|
+
req || {}
|
64
|
+
end
|
65
|
+
|
66
|
+
# ----------------------------------------
|
67
|
+
# GEO
|
68
|
+
#
|
69
|
+
|
70
|
+
def is_a_geo?
|
71
|
+
geolocators.present?
|
72
|
+
end
|
73
|
+
|
74
|
+
rcvr_alias(:is_geo, :is_geo)
|
75
|
+
def receive_is_geo(val)
|
76
|
+
return unless val
|
77
|
+
unless defined?(Icss::Meta::Req::Geolocator) then
|
78
|
+
warn "View helpers can\'t help with geolocators: Icss::Meta::Req::Geolocator type is missing. Is the catalog loaded properly?"
|
79
|
+
return
|
80
|
+
end
|
81
|
+
self.request_decorators = {
|
82
|
+
:anchors => [
|
83
|
+
Icss::Meta::Req::PointWithRadiusGeolocator,
|
84
|
+
Icss::Meta::Req::AddressTextGeolocator,
|
85
|
+
Icss::Meta::Req::TileXYZoomGeolocator,
|
86
|
+
Icss::Meta::Req::BoundingBoxGeolocator,
|
87
|
+
Icss::Meta::Req::IpAddressGeolocator
|
88
|
+
],
|
89
|
+
}
|
90
|
+
end
|
91
|
+
|
92
|
+
def geolocators
|
93
|
+
request_decorators[:anchors]
|
94
|
+
end
|
95
|
+
|
96
|
+
#
|
97
|
+
# Conversion
|
98
|
+
#
|
99
|
+
def to_hash()
|
100
|
+
{
|
101
|
+
:request => summary_of_request_attr,
|
102
|
+
:response => summary_of_response_attr,
|
103
|
+
:doc => doc,
|
104
|
+
:errors => (errors.blank? ? nil : errors),
|
105
|
+
:samples => samples.map(&:to_hash).map(&:compact_blank),
|
106
|
+
:initial_free_qty => initial_free_qty,
|
107
|
+
:price_per_k_in_cents => price_per_k_in_cents,
|
108
|
+
}.compact
|
109
|
+
end
|
110
|
+
def to_json(*args) to_hash.to_json(*args) ; end
|
111
|
+
|
112
|
+
private
|
113
|
+
def summary_of_response_attr
|
114
|
+
case
|
115
|
+
when response.blank? then response
|
116
|
+
when @response_referenceness then response.fullname
|
117
|
+
else response.to_schema.compact_blank
|
118
|
+
end
|
119
|
+
end
|
120
|
+
def summary_of_request_attr
|
121
|
+
request.map do |req|
|
122
|
+
case
|
123
|
+
when req.blank? then req
|
124
|
+
# Is there a case where this needs to be a string and not a hash?
|
125
|
+
# when req.is_reference? then req.type.fullname
|
126
|
+
else req.to_schema.compact_blank
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
73
130
|
end
|
74
131
|
end
|
132
|
+
|
75
133
|
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
module Icss
|
2
|
+
module Meta
|
3
|
+
#
|
4
|
+
# Holds a sample call for a message and its expected response
|
5
|
+
#
|
6
|
+
# You may define the request parameters using an array of parameters
|
7
|
+
# or with the corresponding URL it would render to.
|
8
|
+
#
|
9
|
+
# This file also decorates Icss::Meta::Message and Icss::Meta::Protocol with helper methods for sample calls.
|
10
|
+
#
|
11
|
+
class MessageSample
|
12
|
+
include ReceiverModel
|
13
|
+
field :name, String
|
14
|
+
field :doc, String
|
15
|
+
field :request, Array, :default => []
|
16
|
+
field :response_hsh, Hash # a hash suitable for populating the message's @response@ type
|
17
|
+
field :response, Hash
|
18
|
+
attr_accessor :raw_response # the raw http response from fetching
|
19
|
+
field :error, String
|
20
|
+
field :url, String
|
21
|
+
attr_accessor :message
|
22
|
+
|
23
|
+
# Whips up the class implied by the ICSS type of this message's response,
|
24
|
+
# and populates it using the response hash.
|
25
|
+
def response_obj
|
26
|
+
return if response.blank?
|
27
|
+
message.response.receive(response)
|
28
|
+
end
|
29
|
+
|
30
|
+
# The URL implied by the given hostname and the sample request parameters.
|
31
|
+
#
|
32
|
+
# @param [String] hostname The hostname or hostname:port to include in the URL
|
33
|
+
# @param [Hash] extra_query_params A hash of extra params to in
|
34
|
+
#
|
35
|
+
# The URI expects string values in the hash used to build the query -- if
|
36
|
+
# calling #to_s on a field won't do what you want, clobber the value beforehand.
|
37
|
+
#
|
38
|
+
def full_url hostname, extra_query_params={}
|
39
|
+
host, port = hostname.split(':', 2)
|
40
|
+
u = Addressable::URI.new(:host => host, :port => port, :path => self.path, :scheme => 'http')
|
41
|
+
u.query_values = query_hash(extra_query_params)
|
42
|
+
u
|
43
|
+
end
|
44
|
+
|
45
|
+
def query_hash extra_query_params={}
|
46
|
+
hsh = (@url.present? ? @url.query_values : request.first.to_hash) rescue {}
|
47
|
+
hsh = hsh.merge extra_query_params
|
48
|
+
hsh.each{|k,v| hsh[k] = v.to_s }
|
49
|
+
hsh
|
50
|
+
end
|
51
|
+
|
52
|
+
def path
|
53
|
+
((@url && @url.path).present? ? @url.path : "/#{message.path}" )
|
54
|
+
end
|
55
|
+
|
56
|
+
# @param [String, Addressable::URI]
|
57
|
+
# the URL can be fully-qualified (htttp://api.infochimps.com/this/that?the=other) or relative (this/that?the=other)
|
58
|
+
# and the path must match that of the message.
|
59
|
+
#
|
60
|
+
def url= new_url
|
61
|
+
if new_url.is_a?(String)
|
62
|
+
unless new_url.include?('?') then warn "sample request url should have a '?' introducing its query parameters: {#{new_url}}" ; end
|
63
|
+
new_url = Addressable::URI.parse(new_url)
|
64
|
+
end
|
65
|
+
@url = new_url
|
66
|
+
end
|
67
|
+
|
68
|
+
# retrieve the response from the given host, storing it in response. this
|
69
|
+
# catches all server errors and constructs a dummy response hash if the call
|
70
|
+
# fails.
|
71
|
+
def fetch_response! hostname="", extra_query_params={}
|
72
|
+
self.raw_response = fetch_raw_response( full_url(hostname, extra_query_params) )
|
73
|
+
begin
|
74
|
+
resp_hsh = JSON.load(raw_response.body)
|
75
|
+
rescue StandardError => e
|
76
|
+
warn [" error parsing response: #{e}"].join("\n")
|
77
|
+
self.response = nil
|
78
|
+
self.error = "JsonParseError"
|
79
|
+
return
|
80
|
+
end
|
81
|
+
if raw_response.code == 200
|
82
|
+
self.response = resp_hsh
|
83
|
+
self.error = nil
|
84
|
+
else
|
85
|
+
self.response = nil
|
86
|
+
self.error = resp_hsh["error"]
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
protected
|
91
|
+
|
92
|
+
def fetch_raw_response full_url
|
93
|
+
RestClient.get(full_url.to_s) do |response, request, result|
|
94
|
+
response
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
class Icss::Meta::Message
|
103
|
+
field :samples, Array, :items => Icss::Meta::MessageSample, :default => []
|
104
|
+
end
|
105
|
+
|
106
|
+
class Icss::Meta::Protocol
|
107
|
+
|
108
|
+
#
|
109
|
+
# a hash for dumping to file:
|
110
|
+
# @example: from the whole thing, would dump only this:
|
111
|
+
#
|
112
|
+
# namespace: util.time
|
113
|
+
# protocol: chronic
|
114
|
+
# messages:
|
115
|
+
# parse:
|
116
|
+
# samples:
|
117
|
+
# - url: "?now=5%3A06%3A07%202010-08-08&time_str=Yesterday"
|
118
|
+
# response: { "time": "2010-08-07 05:06:07 UTC", "epoch_seconds": 1281225967 }
|
119
|
+
#
|
120
|
+
def message_samples_hash
|
121
|
+
hsh = { :namespace => namespace, :protocol => protocol, :messages => {} }
|
122
|
+
messages.each do |msg_name, msg|
|
123
|
+
hsh[:messages][msg_name] = { :samples => [] }
|
124
|
+
msg.samples.each do |sample_req|
|
125
|
+
sample_hsh = {
|
126
|
+
:name => sample_req.name,
|
127
|
+
:doc => sample_req.doc,
|
128
|
+
}
|
129
|
+
if sample_req.response.present?
|
130
|
+
then sample_hsh[:response] = sample_req.response
|
131
|
+
else sample_hsh[:error] = sample_req.error
|
132
|
+
end
|
133
|
+
if sample_req.url.present?
|
134
|
+
then sample_hsh[:url] = sample_req.url.to_s
|
135
|
+
else sample_hsh[:request] = sample_req.request
|
136
|
+
end
|
137
|
+
sample_hsh.compact_blank!
|
138
|
+
hsh[:messages][msg_name][:samples] << sample_hsh
|
139
|
+
end
|
140
|
+
end
|
141
|
+
return hsh
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
data/lib/icss/protocol.rb
CHANGED
@@ -1,146 +1,199 @@
|
|
1
1
|
module Icss
|
2
|
+
module Meta
|
3
|
+
|
4
|
+
# predefine so we can use below
|
5
|
+
|
6
|
+
class Message ; end
|
2
7
|
|
3
|
-
#
|
4
|
-
# Describes an Avro Protocol Declaration
|
5
|
-
#
|
6
|
-
# Avro protocols describe RPC interfaces. The Protocol class will receive an
|
7
|
-
# Avro JSON
|
8
|
-
#
|
9
|
-
# A Protocol has the following attributes:
|
10
|
-
#
|
11
|
-
# * protocol, a string, the name of the protocol (required). +name+ is
|
12
|
-
# provided as an alias for +protocol+.
|
13
|
-
#
|
14
|
-
# * namespace, a string that qualifies the name (optional).
|
15
|
-
#
|
16
|
-
# * doc, a string describing this protocol (optional).
|
17
|
-
#
|
18
|
-
# * types, an optional list of definitions of named types (records, enums,
|
19
|
-
# fixed and errors). An error definition is just like a record definition
|
20
|
-
# except it uses "error" instead of "record". Note that forward references
|
21
|
-
# to named types are not permitted.
|
22
|
-
#
|
23
|
-
# * messages, an optional JSON object whose keys are message names and whose
|
24
|
-
# values are objects whose attributes are described below. No two messages
|
25
|
-
# may have the same name.
|
26
|
-
#
|
27
|
-
# The name and namespace qualification rules defined for schema objects apply
|
28
|
-
# to protocols as well: see the documentation for Icss::Type.
|
29
|
-
#
|
30
|
-
# For example, one may define a simple HelloWorld protocol with:
|
31
|
-
#
|
32
|
-
# {
|
33
|
-
# "namespace": "com.acme",
|
34
|
-
# "protocol": "HelloWorld",
|
35
|
-
# "doc": "Protocol Greetings",
|
36
|
-
# "types": [
|
37
|
-
# { "name": "Greeting",
|
38
|
-
# "type": "record",
|
39
|
-
# "fields": [ {"name": "message", "type": "string"} ]},
|
40
|
-
# { "name": "Curse",
|
41
|
-
# "type": "error",
|
42
|
-
# "fields": [ {"name": "message", "type": "string"} ]}
|
43
|
-
# ],
|
44
|
-
# "messages": {
|
45
|
-
# "hello": {
|
46
|
-
# "doc": "Say hello.",
|
47
|
-
# "request": [{"name": "greeting", "type": "Greeting" }],
|
48
|
-
# "response": "Greeting",
|
49
|
-
# "errors": ["Curse"]
|
50
|
-
# }
|
51
|
-
# }
|
52
|
-
# }
|
53
|
-
#
|
54
|
-
class Protocol
|
55
|
-
include Receiver
|
56
|
-
include Receiver::ActsAsHash
|
57
|
-
include Receiver::ActsAsLoadable
|
58
|
-
include Gorillib::Hashlike::TreeMerge
|
59
|
-
include Receiver::ActiveModelShim
|
60
|
-
|
61
|
-
rcvr_accessor :protocol, String, :required => true
|
62
|
-
alias_method :name, :protocol
|
63
|
-
rcvr_accessor :namespace, String
|
64
|
-
rcvr_accessor :doc, String
|
65
8
|
#
|
66
|
-
|
67
|
-
|
68
|
-
#
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
9
|
+
# Describes an Avro Protocol Declaration
|
10
|
+
#
|
11
|
+
# Avro protocols describe RPC interfaces. The Protocol class will receive an
|
12
|
+
# Avro JSON
|
13
|
+
#
|
14
|
+
# A Protocol has the following attributes:
|
15
|
+
#
|
16
|
+
# * protocol, a string, the name of the protocol (required). +name+ is
|
17
|
+
# provided as an alias for +protocol+.
|
18
|
+
#
|
19
|
+
# * namespace, a string that qualifies the name (optional).
|
20
|
+
#
|
21
|
+
# * doc, a string describing this protocol (optional).
|
22
|
+
#
|
23
|
+
# * types, an optional list of definitions of named types (records, enums,
|
24
|
+
# fixed and errors). An error definition is just like a record definition
|
25
|
+
# except it uses "error" instead of "record". Note that forward references
|
26
|
+
# to named types are not permitted.
|
27
|
+
#
|
28
|
+
# * messages, an optional JSON object whose keys are message names and whose
|
29
|
+
# values are objects whose attributes are described below. No two messages
|
30
|
+
# may have the same name.
|
31
|
+
#
|
32
|
+
# The name and namespace qualification rules defined for schema objects apply
|
33
|
+
# to protocols as well: see the documentation for Icss::Meta::Type.
|
34
|
+
#
|
35
|
+
# For example, one may define a simple HelloWorld protocol with:
|
36
|
+
#
|
37
|
+
# {
|
38
|
+
# "namespace": "com.acme",
|
39
|
+
# "protocol": "HelloWorld",
|
40
|
+
# "doc": "Protocol Greetings",
|
41
|
+
# "types": [
|
42
|
+
# { "name": "Greeting",
|
43
|
+
# "type": "record",
|
44
|
+
# "fields": [ {"name": "message", "type": "string"} ]},
|
45
|
+
# { "name": "Curse",
|
46
|
+
# "type": "error",
|
47
|
+
# "fields": [ {"name": "message", "type": "string"} ]}
|
48
|
+
# ],
|
49
|
+
# "messages": {
|
50
|
+
# "hello": {
|
51
|
+
# "doc": "Say hello.",
|
52
|
+
# "request": [{"name": "greeting", "type": "Greeting" }],
|
53
|
+
# "response": "Greeting",
|
54
|
+
# "errors": ["Curse"]
|
55
|
+
# }
|
56
|
+
# }
|
57
|
+
# }
|
58
|
+
#
|
59
|
+
class Protocol
|
60
|
+
include Icss::ReceiverModel
|
61
|
+
include Icss::ReceiverModel::ActsAsCatalog
|
89
62
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
63
|
+
field :protocol, String, :required => true, :validates => { :format => { :with => /\A[A-Za-z_]\w*\Z/, :message => "must start with [A-Za-z_] and contain only [A-Za-z0-9_]." } }
|
64
|
+
alias_method :basename, :protocol
|
65
|
+
field :namespace, String, :required => true, :validates => { :format => { :with => /\A([A-Za-z_]\w*\.?)+\Z/, :message => "Segments that start with [A-Za-z_] and contain only [A-Za-z0-9_], joined by '.'dots" } }
|
66
|
+
field :title, String
|
67
|
+
field :doc, String
|
68
|
+
#
|
69
|
+
field :types, Array, :items => Icss::Meta::TypeFactory, :default => []
|
70
|
+
field :_doc_hints, Hash, :default => {}
|
94
71
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
72
|
+
field :messages, Hash, :values => Icss::Meta::Message, :default => {}
|
73
|
+
field :data_assets, Array, :items => Icss::Meta::DataAsset, :default => []
|
74
|
+
field :code_assets, Array, :items => Icss::Meta::CodeAsset, :default => []
|
75
|
+
field :targets, Hash, :values => Icss::TargetListFactory, :default => {}, :merge_as => :hash_of_arrays
|
76
|
+
|
77
|
+
field :tags, Array, :items => String, :default => []
|
78
|
+
field :categories, Array, :items => String, :default => []
|
79
|
+
field :license_id, String
|
80
|
+
field :credits, Hash, :values=> String, :default => {} # hash of source_ids
|
81
|
+
|
82
|
+
def license
|
83
|
+
Icss::Meta::License.find(license_id) unless license_id.blank?
|
84
|
+
end
|
85
|
+
|
86
|
+
def sources
|
87
|
+
@sources ||= credits.inject(Hash.new){|hash, credit| hash[credit[0].to_sym] = Icss::Meta::Source.find(credit[1]); hash }
|
88
|
+
end
|
99
89
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
messages[nm]
|
104
|
-
end
|
90
|
+
field :under_consideration, Boolean
|
91
|
+
field :update_frequency, String, :validates => { :format => { :with => /daily|weekly|monthly|quarterly|never/ }, :allow_blank => true }
|
92
|
+
rcvr_remaining :_extra_params
|
105
93
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
94
|
+
after_receive(:parent_my_messages) do |hsh|
|
95
|
+
# Set each message's protocol to self, and if the basename wasn't given, set
|
96
|
+
# it using the message's hash key.
|
97
|
+
self.messages.each{|msg_name, msg| msg.protocol = self; msg.basename ||= msg_name }
|
98
|
+
end
|
99
|
+
after_receive(:warn_if_invalid) do |hsh|
|
100
|
+
warn errors.inspect unless valid?
|
101
|
+
warn "Extra params #{_extra_params.keys.inspect} given to #{self.fullname}" if _extra_params.present?
|
102
|
+
end
|
103
|
+
after_receive(:declare_core_types) do |hsh|
|
104
|
+
self.types.each{|type| type.respond_to?(:_schema) && (type._schema.is_core = (self.fullname == "icss.core.typedefs")) }
|
105
|
+
end
|
106
|
+
after_receive(:fix_legacy_catalog_info) do
|
107
|
+
if targets[:catalog].present?
|
108
|
+
catalog = targets[:catalog].first
|
109
|
+
if self.title.blank? then self.title = catalog.title ; end
|
110
|
+
if self.tags.blank? then self.tags = catalog.tags ; end
|
111
|
+
if self.doc.blank? then self.doc = catalog.description; end
|
112
|
+
end
|
113
|
+
end
|
111
114
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
tgts.each do |target_name, target_info_list|
|
116
|
-
targets[target_name] = TargetListFactory.receive(target_info_list, target_name) # array of targets
|
115
|
+
# String: namespace.basename
|
116
|
+
def fullname
|
117
|
+
[namespace, basename].compact.join(".")
|
117
118
|
end
|
118
|
-
targets
|
119
|
-
end
|
120
119
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
:doc => doc,
|
126
|
-
:under_consideration => under_consideration,
|
127
|
-
:update_frequency => update_frequency,
|
128
|
-
:types => (types && types.map(&:to_hash)),
|
129
|
-
:messages => messages.inject({}){|h,(k,v)| h[k] = v.to_hash; h },
|
130
|
-
:data_assets => data_assets.map(&:to_hash).map(&:compact_blank),
|
131
|
-
:code_assets => code_assets.map(&:to_hash).map(&:compact_blank),
|
132
|
-
:targets => targets_to_hash,
|
133
|
-
}.reject{|k,v| v.nil? }
|
134
|
-
end
|
120
|
+
# a / separated version of the fullname, with no / at start
|
121
|
+
def path
|
122
|
+
fullname.gsub('.', '/')
|
123
|
+
end
|
135
124
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
125
|
+
def find_message nm
|
126
|
+
return if messages.blank?
|
127
|
+
nm = nm.to_s.gsub("/", ".").split(".").last
|
128
|
+
messages[nm]
|
140
129
|
end
|
141
|
-
end
|
142
130
|
|
143
|
-
|
144
|
-
|
131
|
+
def receive_types(types)
|
132
|
+
# this is a horrible, horrible kludge so that types with simple names ('bob') can become
|
133
|
+
# properly namespaced ('foo.bar.bob') even when they haven't met their parents (and even
|
134
|
+
# where the calls to receive types are nested/recursive)
|
135
|
+
Icss::Meta::TypeFactory.with_namespace(namespace) do
|
136
|
+
super(types)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def receive_messages(types)
|
141
|
+
# this is a horrible, horrible kludge so that messages with simple names ('do_bob') can become
|
142
|
+
# properly namespaced ('foo.bar.do_bob') even when they haven't met their parents (and even
|
143
|
+
# where the calls to receive_messages are nested/recursive)
|
144
|
+
Icss::Meta::TypeFactory.with_namespace(namespace) do
|
145
|
+
super(types)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def receive_protocol(nm)
|
150
|
+
name_segs = nm.to_s.gsub("/", ".").split(".")
|
151
|
+
self.protocol = name_segs.pop
|
152
|
+
self.namespace = name_segs.join('.') if name_segs.present?
|
153
|
+
end
|
154
|
+
|
155
|
+
def receive_targets(tgts)
|
156
|
+
return unless tgts.present?
|
157
|
+
self.targets ||= {}
|
158
|
+
tgts.symbolize_keys!.each do |target_name, target_info_list|
|
159
|
+
targets[target_name] = TargetListFactory.receive(target_info_list, target_name) # array of targets
|
160
|
+
end
|
161
|
+
targets
|
162
|
+
end
|
163
|
+
|
164
|
+
def self.catalog_sections
|
165
|
+
['core', 'datasets', 'old']
|
166
|
+
end
|
167
|
+
|
168
|
+
def to_hash()
|
169
|
+
{
|
170
|
+
:namespace => @namespace, # use accessor so unset namespace isn't given
|
171
|
+
:protocol => protocol,
|
172
|
+
:license_id => license_id,
|
173
|
+
:credits => credits,
|
174
|
+
:tags => tags,
|
175
|
+
:categories => categories,
|
176
|
+
:doc => doc,
|
177
|
+
:types => (types && types.map(&:to_schema)),
|
178
|
+
:messages => messages.inject({}){|h,(k,v)| h[k.to_sym] = v.to_hash; h },
|
179
|
+
:data_assets => data_assets.map(&:to_hash).map(&:compact_blank),
|
180
|
+
:code_assets => code_assets.map(&:to_hash).map(&:compact_blank),
|
181
|
+
:update_frequency => update_frequency,
|
182
|
+
:under_consideration => under_consideration,
|
183
|
+
:targets => targets_to_hash,
|
184
|
+
}.reject{|k,v| v.nil? }
|
185
|
+
end
|
186
|
+
|
187
|
+
def targets_to_hash
|
188
|
+
return unless targets
|
189
|
+
targets.inject({}) do |hsh,(k,targs)|
|
190
|
+
hsh[k] = targs.map(&:to_hash).map(&:compact_blank) ; hsh
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
# This will cause funny errors when it is an element of something that's to_json'ed
|
195
|
+
def to_json(*args) to_hash.to_json(*args) ; end
|
196
|
+
end
|
145
197
|
end
|
198
|
+
|
146
199
|
end
|