icss 0.1.3 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (98) hide show
  1. data/.watchr +35 -3
  2. data/CHANGELOG.md +38 -0
  3. data/Gemfile +19 -14
  4. data/README.md +296 -0
  5. data/Rakefile +2 -6
  6. data/TODO.md +13 -0
  7. data/VERSION +1 -1
  8. data/examples/avro_examples/complicated.icss.yaml +14 -13
  9. data/examples/bnc.icss.yaml +70 -0
  10. data/examples/chronic.icss.yaml +3 -3
  11. data/examples/license.icss.yaml +7 -0
  12. data/examples/source1.icss.yaml +4 -0
  13. data/examples/source2.icss.yaml +4 -0
  14. data/examples/test_icss.yaml +67 -0
  15. data/icss.gemspec +103 -43
  16. data/lib/icss.rb +37 -15
  17. data/lib/icss/core_types.rb +19 -0
  18. data/lib/icss/error.rb +4 -0
  19. data/{init.rb → lib/icss/init.rb} +0 -0
  20. data/lib/icss/message.rb +124 -66
  21. data/lib/icss/message/message_sample.rb +144 -0
  22. data/lib/icss/protocol.rb +184 -131
  23. data/lib/icss/protocol/code_asset.rb +18 -0
  24. data/lib/icss/protocol/data_asset.rb +23 -0
  25. data/lib/icss/protocol/license.rb +41 -0
  26. data/lib/icss/protocol/source.rb +37 -0
  27. data/lib/icss/protocol/target.rb +68 -0
  28. data/lib/icss/receiver_model.rb +24 -0
  29. data/lib/icss/receiver_model/active_model_shim.rb +36 -0
  30. data/lib/icss/receiver_model/acts_as_catalog.rb +170 -0
  31. data/lib/icss/receiver_model/acts_as_hash.rb +177 -0
  32. data/lib/icss/receiver_model/acts_as_loadable.rb +47 -0
  33. data/lib/icss/receiver_model/acts_as_tuple.rb +100 -0
  34. data/lib/icss/receiver_model/locale/en.yml +27 -0
  35. data/lib/icss/receiver_model/to_geo_json.rb +19 -0
  36. data/lib/icss/receiver_model/tree_merge.rb +34 -0
  37. data/lib/icss/receiver_model/validations.rb +31 -0
  38. data/lib/icss/serialization.rb +51 -0
  39. data/lib/icss/serialization/zaml.rb +443 -0
  40. data/lib/icss/type.rb +148 -501
  41. data/lib/icss/type/base_type.rb +0 -0
  42. data/lib/icss/type/named_type.rb +184 -0
  43. data/lib/icss/type/record_field.rb +77 -0
  44. data/lib/icss/type/record_model.rb +49 -0
  45. data/lib/icss/type/record_schema.rb +54 -0
  46. data/lib/icss/type/record_type.rb +325 -0
  47. data/lib/icss/type/simple_types.rb +72 -0
  48. data/lib/icss/type/structured_schema.rb +288 -0
  49. data/lib/icss/type/type_factory.rb +144 -0
  50. data/lib/icss/type/union_schema.rb +41 -0
  51. data/lib/icss/view_helper.rb +56 -19
  52. data/notes/named_array.md +32 -0
  53. data/notes/on_include_vs_extend_etc.rb +176 -0
  54. data/notes/technical_details.md +278 -0
  55. data/spec/core_types_spec.rb +119 -0
  56. data/spec/fixtures/zaml_complex_hash.yaml +35 -0
  57. data/spec/icss_spec.rb +86 -23
  58. data/spec/message/message_sample_spec.rb +4 -0
  59. data/spec/message_spec.rb +139 -0
  60. data/spec/protocol/license_spec.rb +67 -0
  61. data/spec/protocol/protocol_catalog_spec.rb +48 -0
  62. data/spec/protocol/protocol_validations_spec.rb +176 -0
  63. data/spec/protocol/source_spec.rb +65 -0
  64. data/spec/protocol_spec.rb +91 -37
  65. data/spec/receiver_model_spec.rb +111 -0
  66. data/spec/serialization/zaml_spec.rb +81 -0
  67. data/spec/serialization/zaml_test.rb +473 -0
  68. data/spec/serialization_spec.rb +63 -0
  69. data/spec/spec_helper.rb +24 -7
  70. data/spec/support/icss_test_helper.rb +67 -0
  71. data/spec/support/load_example_protocols.rb +17 -0
  72. data/spec/type/base_type_spec.rb +0 -0
  73. data/spec/type/named_type_spec.rb +75 -0
  74. data/spec/type/record_field_spec.rb +44 -0
  75. data/spec/type/record_model_spec.rb +206 -0
  76. data/spec/type/record_schema_spec.rb +161 -0
  77. data/spec/type/record_type_spec.rb +155 -0
  78. data/spec/type/simple_types_spec.rb +121 -0
  79. data/spec/type/structured_schema_spec.rb +300 -0
  80. data/spec/type/type_catalog_spec.rb +44 -0
  81. data/spec/type/type_factory_spec.rb +93 -0
  82. data/spec/type/union_schema_spec.rb +0 -0
  83. data/spec/type_spec.rb +63 -0
  84. metadata +205 -144
  85. data/CHANGELOG.textile +0 -9
  86. data/Gemfile.lock +0 -40
  87. data/README.textile +0 -29
  88. data/lib/icss/brevity.rb +0 -136
  89. data/lib/icss/code_asset.rb +0 -16
  90. data/lib/icss/core_ext.rb +0 -9
  91. data/lib/icss/data_asset.rb +0 -22
  92. data/lib/icss/old.rb +0 -96
  93. data/lib/icss/protocol_set.rb +0 -48
  94. data/lib/icss/sample_message_call.rb +0 -142
  95. data/lib/icss/target.rb +0 -72
  96. data/lib/icss/type/factory.rb +0 -196
  97. data/lib/icss/validations.rb +0 -16
  98. 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
@@ -0,0 +1,4 @@
1
+ module Icss
2
+ class NotFoundError < ::NameError ; end unless defined?(NotFoundError)
3
+ class FactoryTypeMissing < ::ArgumentError ; end unless defined?(FactoryTypeMissing)
4
+ end
File without changes
@@ -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
- # 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 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
- def path
43
- File.join(protocol.path, name)
44
- end
26
+ field :request_decorators, Hash, :default => {:anchors => []}
45
27
 
46
- def first_sample_request_param
47
- req = samples.first.request.first rescue nil
48
- req || {}
49
- end
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
- private
68
- def summary_of_response_attr
69
- case when response.blank? then response when @response_is_reference then response.name else response.to_hash.compact_blank end
70
- end
71
- def summary_of_request_attr
72
- request.map(&:to_hash).compact_blank
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
@@ -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
- rcvr_accessor :types, Array, :of => Icss::TypeFactory, :default => []
67
- rcvr_accessor :messages, Hash, :of => Icss::Message, :default => {}
68
- # extensions to avro
69
- rcvr_accessor :data_assets, Array, :of => Icss::DataAsset, :default => []
70
- rcvr_accessor :code_assets, Array, :of => Icss::CodeAsset, :default => []
71
- rcvr_accessor :targets, Hash, :of => Icss::TargetListFactory, :default => {}, :merge_as => :hash_of_arrays
72
- rcvr_accessor :under_consideration, Boolean
73
- rcvr_accessor :update_frequency, String
74
-
75
- validates_presence_of :protocol, :namespace
76
- validates_format_of :protocol, :with => /\A[A-Za-z_]\w*\z/, :message => "must start with [A-Za-z_] and contain only [A-Za-z0-9_].", :allow_blank => true
77
- validates_format_of :namespace, :with => /\A([A-Za-z_]\w*\.?)+\z/, :message => "must be a dot-separated sequence of avro names (start with [A-Za-z_] and contain only [A-Za-z0-9_])", :allow_blank => true
78
- validates_format_of :update_frequency, :with => /daily|weekly|monthly|quarterly|never/, :message => "must be one of daily, weekly, monthly, quarterly, never", :allow_blank => true
79
-
80
- after_receive do |hsh|
81
- # Set each message's protocol to self, and if the name wasn't given, set
82
- # it using the message's hash key.
83
- self.messages.each{|msg_name, msg| msg.protocol = self; msg.name ||= msg_name }
84
- # Set each type's parent to self (for namespace resolution)
85
- self.types.each{|type| type.parent = self }
86
- # warn if invalid
87
- warn errors.inspect unless valid?
88
- end
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
- # String: namespace.name
91
- def fullname
92
- [namespace, name].compact.join(".")
93
- end
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
- # a / separated version of the name, with no / at start
96
- def path
97
- fullname.gsub('.', '/')
98
- end
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
- def find_message nm
101
- return if messages.blank?
102
- nm = nm.to_s.gsub("/", ".").split(".").last
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
- def receive_protocol nm
107
- namespace_and_name = nm.to_s.gsub("/", ".").split(".")
108
- self.protocol = namespace_and_name.pop
109
- self.namespace = namespace_and_name.join('.')
110
- end
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
- def receive_targets tgts
113
- return unless tgts.present?
114
- self.targets ||= {}
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
- def to_hash()
122
- {
123
- :namespace => @namespace, # use accessor so unset namespace isn't given
124
- :protocol => protocol,
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
- def targets_to_hash
137
- return unless targets
138
- targets.inject({}) do |hsh,(k,targs)|
139
- hsh[k] = targs.map(&:to_hash).map(&:compact_blank) ; hsh
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
- # This will cause funny errors when it is an element of something that's to_json'ed
144
- def to_json(*args) to_hash.to_json(*args) ; end
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