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.
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