icss 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. data/.document +5 -0
  2. data/.rspec +3 -0
  3. data/.watchr +20 -0
  4. data/CHANGELOG.textile +8 -0
  5. data/Gemfile +17 -0
  6. data/Gemfile.lock +34 -0
  7. data/LICENSE.textile +20 -0
  8. data/README.textile +19 -0
  9. data/Rakefile +43 -0
  10. data/VERSION +1 -0
  11. data/examples/BulkData.avpr +21 -0
  12. data/examples/complicated.icss.yaml +158 -0
  13. data/examples/interop.avsc +32 -0
  14. data/examples/mail.avpr +20 -0
  15. data/examples/namespace.avpr +28 -0
  16. data/examples/org/apache/avro/ipc/HandshakeRequest.avsc +11 -0
  17. data/examples/org/apache/avro/ipc/HandshakeResponse.avsc +15 -0
  18. data/examples/org/apache/avro/ipc/trace/avroTrace.avdl +64 -0
  19. data/examples/org/apache/avro/ipc/trace/avroTrace.avpr +82 -0
  20. data/examples/org/apache/avro/mapred/tether/InputProtocol.avpr +59 -0
  21. data/examples/org/apache/avro/mapred/tether/OutputProtocol.avpr +75 -0
  22. data/examples/simple.avpr +70 -0
  23. data/examples/weather.avsc +9 -0
  24. data/icss.gemspec +104 -0
  25. data/icss_specification.textile +370 -0
  26. data/init.rb +3 -0
  27. data/lib/icss.rb +19 -0
  28. data/lib/icss/brevity.rb +136 -0
  29. data/lib/icss/code_asset.rb +16 -0
  30. data/lib/icss/core_ext.rb +4 -0
  31. data/lib/icss/data_asset.rb +22 -0
  32. data/lib/icss/message.rb +72 -0
  33. data/lib/icss/old.rb +96 -0
  34. data/lib/icss/protocol.rb +138 -0
  35. data/lib/icss/protocol_set.rb +48 -0
  36. data/lib/icss/sample_message_call.rb +140 -0
  37. data/lib/icss/target.rb +71 -0
  38. data/lib/icss/type.rb +517 -0
  39. data/lib/icss/type/factory.rb +196 -0
  40. data/lib/icss/validations.rb +16 -0
  41. data/lib/icss/view_helper.rb +28 -0
  42. data/spec/icss_spec.rb +7 -0
  43. data/spec/spec_helper.rb +31 -0
  44. metadata +218 -0
@@ -0,0 +1,16 @@
1
+ module Icss
2
+ class CodeAsset
3
+ include Receiver
4
+ include Receiver::ActsAsHash
5
+
6
+ rcvr_accessor :name, String
7
+ rcvr_accessor :location, String
8
+
9
+ def to_hash()
10
+ { :name => name, :location => location}
11
+ end
12
+
13
+ def to_json() to_hash.to_json ; end
14
+
15
+ end
16
+ end
@@ -0,0 +1,4 @@
1
+ require 'gorillib/string/inflections'
2
+ require 'gorillib/string/constantize'
3
+ require 'gorillib/hash/keys'
4
+ require 'gorillib/metaprogramming/class_attribute'
@@ -0,0 +1,22 @@
1
+ module Icss
2
+ class DataAsset
3
+ include Receiver
4
+ include Receiver::ActsAsHash
5
+
6
+ rcvr_accessor :name, String
7
+ rcvr_accessor :location, String
8
+ #overriding ruby's deprecated but still present type attr on objects
9
+ attr_accessor :type
10
+ rcvr_accessor :type, String
11
+ rcvr_accessor :doc, String
12
+
13
+ def named? nm
14
+ name == nm
15
+ end
16
+
17
+ def to_hash()
18
+ { :name => name, :location => location, :type => type, :doc => doc }
19
+ end
20
+ def to_json() to_hash.to_json ; end
21
+ end
22
+ end
@@ -0,0 +1,72 @@
1
+ module Icss
2
+
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
41
+
42
+ def path
43
+ File.join(protocol.path, name)
44
+ end
45
+
46
+ def first_sample_request_param
47
+ req = samples.first.request.first rescue nil
48
+ req || {}
49
+ end
50
+
51
+ #
52
+ # Conversion
53
+ #
54
+ def to_hash()
55
+ {
56
+ :doc => doc,
57
+ :request => summary_of_request_attr,
58
+ :response => summary_of_response_attr,
59
+ :errors => errors,
60
+ }.reject{|k,v| v.nil? }
61
+ end
62
+ def to_json(*args) to_hash.to_json(*args) ; end
63
+
64
+ private
65
+ def summary_of_response_attr
66
+ case when response.blank? then response when @response_is_reference then response.name else response.to_hash end
67
+ end
68
+ def summary_of_request_attr
69
+ request.map(&:to_hash)
70
+ end
71
+ end
72
+ end
data/lib/icss/old.rb ADDED
@@ -0,0 +1,96 @@
1
+ module Icss
2
+ Protocol.class_eval do
3
+
4
+ #
5
+ # Returns the asset hash with the passed in asset_name
6
+ #
7
+ def asset_for_name nm
8
+ warn "asset_for_name obsolete, please rewrite"
9
+ data_assets.find{|asset| asset.named?(nm) }
10
+ end
11
+
12
+ #
13
+ # Returns the full type for the passed in asset hash
14
+ #
15
+ def type_for_asset asset
16
+ warn "type_for_asset obsolete, please rewrite"
17
+ # types.find{|type| type.name == asset['type']}
18
+ raise 'use asset.type_obj'
19
+ end
20
+
21
+ #
22
+ # Given an asset name, returns the the record it points to
23
+ #
24
+ def type_for_asset_name asset_name
25
+ warn "type_for_asset_name obsolete, please rewrite"
26
+ asset = asset_for_name(asset_name)
27
+ type_for_asset(asset)
28
+ end
29
+
30
+ #
31
+ # Given an asset name, return the name of the type it points to
32
+ #
33
+ def type_name_for_asset_name asset_name
34
+ warn "type_name_for_asset_name obsolete, please rewrite"
35
+ asset = asset_for_name(asset_name)
36
+ asset['type']
37
+ end
38
+
39
+ #
40
+ # Return the avro record with the given name
41
+ #
42
+ def type_for_name type_name
43
+ warn "type_for_name obsolete, please rewrite"
44
+ # types.find{|record| record.name == type_name}
45
+ Icss::Type.find(type_name)
46
+ end
47
+
48
+ #
49
+ # Fetch the avro fields for a named data asset
50
+ #
51
+ def fields_for_asset asset_name
52
+ warn "fields_for_asset obsolete, please rewrite"
53
+ asset = asset_for_name(asset_name)
54
+ asset_type = type_for_asset(asset)
55
+ asset_type.fields
56
+ end
57
+
58
+ #
59
+ # Fetch the locations on disk of each data asset. WARNING: Assuming relative paths
60
+ #
61
+ def location_for_asset asset_name
62
+ warn "location_for_asset obsolete, please rewrite"
63
+ asset = asset_for_name(asset_name)
64
+ File.join(dirname, asset['location'])
65
+ end
66
+
67
+ #
68
+ # Return the index of the named field for the named type. Returns (nil) if field
69
+ # does not exist
70
+ #
71
+ def index_of_fieldname asset_name, field_name
72
+ warn "index_of_fieldname obsolete, please rewrite"
73
+ type = type_for_asset_name(asset_name)
74
+ type.index_of_fieldname(field_name)
75
+ end
76
+
77
+ AVRO_PIG_MAPPING = {
78
+ 'string' => 'chararray',
79
+ 'int' => 'int',
80
+ 'long' => 'long',
81
+ 'float' => 'float',
82
+ 'double' => 'double',
83
+ 'bytes' => 'bytearray',
84
+ 'fixed' => 'bytearray'
85
+ }
86
+
87
+ #
88
+ # Add pig fields to a passed in array of avro fields
89
+ #
90
+ def augment_with_pig_fields fields
91
+ warn "augment_with_pig_fields obsolete, please rewrite"
92
+ fields.map{|field| field.body['pig_type'] = AVRO_PIG_MAPPING[field.type]; field }
93
+ end
94
+ end
95
+
96
+ end
@@ -0,0 +1,138 @@
1
+ module Icss
2
+
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 Icss::Validations
59
+
60
+ rcvr_accessor :protocol, String, :required => true
61
+ alias_method :name, :protocol
62
+ rcvr_accessor :namespace, String # must be *dotted* ("foo.bar"), not slashed ("foo/bar")
63
+ rcvr_accessor :doc, String
64
+ #
65
+ rcvr_accessor :types, Array, :of => Icss::TypeFactory, :default => []
66
+ rcvr_accessor :messages, Hash, :of => Icss::Message, :default => {}
67
+ # extensions to avro
68
+ rcvr_accessor :data_assets, Array, :of => Icss::DataAsset, :default => []
69
+ rcvr_accessor :code_assets, Array, :of => Icss::CodeAsset, :default => []
70
+ rcvr_accessor :targets, Hash, :of => Icss::TargetListFactory, :default => {}, :merge_as => :hash_of_arrays
71
+ rcvr_accessor :under_consideration, Boolean
72
+ rcvr_accessor :update_frequency, String # must be of the form daily, weekly, monthly, quarterly, never
73
+
74
+ # attr_accessor :body
75
+ after_receive do |hsh|
76
+ # Set each message's protocol to self, and if the name wasn't given, set
77
+ # it using the message's hash key.
78
+ self.messages.each{|msg_name, msg| msg.protocol = self; msg.name ||= msg_name }
79
+ # Set all the type's parent to self (for namespace resolution)
80
+ self.types.each{|type| type.parent = self }
81
+ validate_name
82
+ validate_namespace
83
+ end
84
+
85
+ # String: namespace.name
86
+ def fullname
87
+ [namespace, name].compact.join(".")
88
+ end
89
+
90
+ def path
91
+ fullname.gsub('.', '/')
92
+ end
93
+
94
+ def find_message nm
95
+ return if messages.blank?
96
+ nm = nm.to_s.gsub("/", ".").split(".").last
97
+ messages[nm]
98
+ end
99
+
100
+ def receive_protocol nm
101
+ namespace_and_name = nm.to_s.gsub("/", ".").split(".")
102
+ self.protocol = namespace_and_name.pop
103
+ self.namespace = namespace_and_name.join('.')
104
+ end
105
+
106
+ def receive_targets hsh
107
+ self.targets = hsh.inject({}) do |target_obj_hsh, (target_name, target_info_list)|
108
+ target_obj_hsh[target_name] = TargetListFactory.receive(target_info_list, target_name) # returns an arry of targets
109
+ target_obj_hsh
110
+ end
111
+ end
112
+
113
+ def to_hash()
114
+ {
115
+ :namespace => @namespace, # use accessor so unset namespace isn't given
116
+ :protocol => protocol,
117
+ :doc => doc,
118
+ :under_consideration => under_consideration,
119
+ :update_frequency => update_frequency,
120
+ :types => (types && types.map(&:to_hash)),
121
+ :messages => messages.inject({}){|h,(k,v)| h[k] = v.to_hash; h },
122
+ :data_assets => data_assets.map(&:to_hash),
123
+ :code_assets => code_assets.map(&:to_hash),
124
+ :targets => targets_to_hash,
125
+ }.reject{|k,v| v.nil? }.tree_merge! self.message_samples_hash
126
+ end
127
+
128
+ def targets_to_hash
129
+ return unless targets
130
+ targets.inject({}) do |hsh,(k,targs)|
131
+ hsh[k] = targs.map{|t| t.respond_to?(:to_hash) ? t.to_hash : t } ; hsh
132
+ end
133
+ end
134
+
135
+ # This will cause funny errors when it is an element of something that's to_json'ed
136
+ def to_json(*args) to_hash.to_json(*args) ; end
137
+ end
138
+ end
@@ -0,0 +1,48 @@
1
+ # you must require 'icss/protocol_set' explicitly to use it.
2
+
3
+ module Icss
4
+ #
5
+ # Holds a set of icss protocols, with helper methods to load them from a
6
+ # directory tree, to merge in a protocol set yaml file, and so forth
7
+ #
8
+ class ProtocolSet
9
+ attr_accessor :protocols
10
+
11
+ def initialize
12
+ self.protocols = {}
13
+ end
14
+
15
+
16
+ def register pr
17
+ if protocols[pr.fullname]
18
+ protocols[pr.fullname].tree_merge!(pr)
19
+ else
20
+ protocols[pr.fullname] = pr
21
+ end
22
+ end
23
+
24
+ # load and register the protocols in the given filenames
25
+ def load_protocols *icss_filenames
26
+ icss_filenames = icss_filenames.flatten.compact
27
+ icss_filenames.each do |icss_filename|
28
+ register Icss::Protocol.receive_from_file(icss_filename)
29
+ end
30
+ end
31
+
32
+ # load and register the set of protocols in the given yaml file.
33
+ # it must be a hash with element 'protocol_set', holding an array of
34
+ # protocol hashes.
35
+ #
36
+ # protocol_set:
37
+ # - namespace: "..."
38
+ # protocol: "..."
39
+ def load_protocol_set protocol_set_filename
40
+ protocol_set_hsh = YAML.load(File.open(protocol_set_filename))
41
+ protocol_set_hsh['protocol_set'].each do |hsh|
42
+ register Icss::Protocol.receive(hsh)
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+
@@ -0,0 +1,140 @@
1
+ module Icss
2
+ #
3
+ # Holds a sample call for a message and its expected response
4
+ #
5
+ # You may define the request parameters using an array of parameters
6
+ # or with the corresponding URL it would render to.
7
+ #
8
+ # This file also decorates Icss::Message and Icss::Protocol with helper methods for sample calls.
9
+ #
10
+ class SampleMessageCall
11
+ include Receiver
12
+ rcvr_accessor :name, String
13
+ rcvr_accessor :doc, String
14
+ rcvr_accessor :request, Array, :default => []
15
+ rcvr_accessor :response, Object # a hash suitable for populating the message's @response@ type
16
+ attr_accessor :raw_response # the raw http response from fetching
17
+ rcvr_accessor :error, String
18
+ rcvr_accessor :url, String
19
+ attr_accessor :message
20
+
21
+ # The URL implied by the given hostname and the sample request parameters.
22
+ #
23
+ # @param [String] hostname The hostname or hostname:port to include in the URL
24
+ # @param [Hash] extra_query_params A hash of extra params to in
25
+ #
26
+ # The URI expects string values in the hash used to build the query -- if
27
+ # calling #to_s on a field won't do what you want, clobber the value beforehand.
28
+ #
29
+ def full_url hostname, extra_query_params={}
30
+ host, port = hostname.split(':', 2)
31
+ u = Addressable::URI.new(:host => host, :port => port, :path => self.path, :scheme => 'http')
32
+ u.query_values = query_hash(extra_query_params)
33
+ u
34
+ end
35
+
36
+ def query_hash extra_query_params={}
37
+ hsh = (@url.present? ? @url.query_values : request.first.to_hash) rescue {}
38
+ hsh = hsh.merge extra_query_params
39
+ hsh.each{|k,v| hsh[k] = v.to_s }
40
+ hsh
41
+ end
42
+
43
+ def path
44
+ ((@url && @url.path).present? ? @url.path : "/#{message.path}" )
45
+ end
46
+
47
+ # @param [String, Addressable::URI]
48
+ # the URL can be fully-qualified (htttp://api.infochimps.com/this/that?the=other) or relative (this/that?the=other)
49
+ # and the path must match that of the message.
50
+ #
51
+ def url= new_url
52
+ if new_url.is_a?(String)
53
+ unless new_url.include?('?') then warn "sample request url should have a '?' introducing its query parameters: {#{new_url}}" ; end
54
+ new_url = Addressable::URI.parse(new_url)
55
+ end
56
+ @url = new_url
57
+ end
58
+
59
+ # Whips up the class implied by the ICSS type of this message's response,
60
+ # and populates it using the response hash.
61
+ def response_obj
62
+ return if response.blank?
63
+ klass = message.response.ruby_klass
64
+ klass.receive(response.compact_blank!)
65
+ end
66
+
67
+ # retrieve the response from the given host, storing it in response. this
68
+ # catches all server errors and constructs a dummy response hash if the call
69
+ # fails.
70
+ def fetch_response! hostname="", extra_query_params={}
71
+ self.raw_response = fetch_raw_response( full_url(hostname, extra_query_params) )
72
+ begin
73
+ resp_hsh = JSON.load(raw_response.body)
74
+ rescue StandardError => e
75
+ warn [" error parsing response: #{e}"].join("\n")
76
+ self.response = nil
77
+ self.error = "JsonParseError"
78
+ return
79
+ end
80
+ if raw_response.code == 200
81
+ self.response = resp_hsh
82
+ self.error = nil
83
+ else
84
+ self.response = nil
85
+ self.error = resp_hsh["error"]
86
+ end
87
+ end
88
+
89
+ private
90
+
91
+ def fetch_raw_response full_url
92
+ RestClient.get(full_url.to_s) do |response, request, result|
93
+ response
94
+ end
95
+ end
96
+ end
97
+ end
98
+
99
+ class Icss::Message
100
+ rcvr_accessor :samples, Array, :of => Icss::SampleMessageCall, :default => []
101
+ end
102
+
103
+ class Icss::Protocol
104
+
105
+ #
106
+ # a hash for dumping to file:
107
+ # @example: from the whole thing, would dump only this:
108
+ #
109
+ # namespace: util.time
110
+ # protocol: chronic
111
+ # messages:
112
+ # parse:
113
+ # samples:
114
+ # - url: "?now=5%3A06%3A07%202010-08-08&time_str=Yesterday"
115
+ # response: { "time": "2010-08-07 05:06:07 UTC", "epoch_seconds": 1281225967 }
116
+ #
117
+ def message_samples_hash
118
+ hsh = { "namespace" => namespace, "protocol" => protocol, "messages" => {} }
119
+ messages.each do |msg_name, msg|
120
+ hsh["messages"][msg_name] = { "samples" => [] }
121
+ msg.samples.each do |sample_req|
122
+ sample_hsh = {
123
+ "name" => sample_req.name,
124
+ "doc" => sample_req.doc,
125
+ }
126
+ if sample_req.response.present?
127
+ then sample_hsh['response'] = sample_req.response
128
+ else sample_hsh['error'] = sample_req.error
129
+ end
130
+ if sample_req.url.present?
131
+ then sample_hsh['url'] = sample_req.url.to_s
132
+ else sample_hsh['request'] = sample_req.request
133
+ end
134
+ hsh["messages"][msg_name]["samples"] << sample_hsh.compact_blank!
135
+ end
136
+ end
137
+ return hsh
138
+ end
139
+
140
+ end