Module: Safrano

Includes:
XMLNS
Included in:
ServiceBase
Defined in:
lib/odata/error.rb,
lib/odata/batch.rb,
lib/odata/entity.rb,
lib/odata/expand.rb,
lib/odata/select.rb,
lib/odata/walker.rb,
lib/safrano/core.rb,
lib/odata/attribute.rb,
lib/odata/model_ext.rb,
lib/odata/relations.rb,
lib/safrano/request.rb,
lib/safrano/service.rb,
lib/safrano/service.rb,
lib/safrano/service.rb,
lib/safrano/version.rb,
lib/odata/collection.rb,
lib/odata/transition.rb,
lib/safrano/contract.rb,
lib/safrano/rack_app.rb,
lib/safrano/response.rb,
lib/core_ext/Dir/iter.rb,
lib/odata/filter/base.rb,
lib/odata/filter/tree.rb,
lib/odata/complex_type.rb,
lib/odata/filter/error.rb,
lib/odata/filter/parse.rb,
lib/odata/filter/token.rb,
lib/odata/request/json.rb,
lib/core_ext/String/edm.rb,
lib/odata/filter/sequel.rb,
lib/safrano/deprecation.rb,
lib/core_ext/Date/format.rb,
lib/core_ext/Integer/edm.rb,
lib/core_ext/Time/format.rb,
lib/odata/url_parameters.rb,
lib/safrano/type_mapping.rb,
lib/odata/function_import.rb,
lib/odata/collection_media.rb,
lib/odata/collection_order.rb,
lib/core_ext/Hash/transform.rb,
lib/core_ext/String/convert.rb,
lib/odata/collection_filter.rb,
lib/core_ext/DateTime/format.rb,
lib/core_ext/Numeric/convert.rb,
lib/odata/edm/primitive_types.rb,
lib/odata/navigation_attribute.rb,
lib/core_ext/MatchData/matchlen.rb,
lib/core_ext/REXML/Document/output.rb,
lib/odata/filter/sequel_datetime_adapter.rb,
lib/odata/filter/sequel_function_adapter.rb

Overview

mach_length for ruby < 3.0

Defined Under Namespace

Modules: API, Batch, Contract, CoreExt, CoreIncl, Deprecation, Edm, Entity, EntityBase, EntityClassBase, EntityClassMedia, EntityClassMultiPK, EntityClassNonMedia, EntityClassSinglePK, EntityCreateArrayOutput, EntityCreateStandardOutput, EntityMultiPK, EntitySinglePK, ErrorClass, ErrorInstance, ExpandHandler, Filter, FunctionImport, Media, MediaEntity, MethodHandlers, NavigationInfo, NonMediaEntity, OData, PKUriWithFunc, PKUriWithoutFunc, RFC2047, Transitions, XJSON, XMLNS Classes: AlreadyExistsUnprocessableError, Attribute, AttributeTypeMapping, BadRequestEmptyMediaUpload, BadRequestError, BadRequestExpandInvalidPath, BadRequestFailedChangeSet, BadRequestFilterParseError, BadRequestInlineCountParamError, BadRequestInvalidAttribPath, BadRequestNonMediaValue, BadRequestOrderParseError, BadRequestSelectInvalidProps, BadRequestSequelAdapterError, BatchNotImplementedError, ComplexType, ErrorNotFound, ErrorNotFoundSegment, Expand, ExpandBase, FilterBase, FilterByParse, FilterFunctionNotImplementedError, FilterParseError, FilterParseErrorWrongColumnName, FilterParseWrappedError, FilterUnknownFunctionError, InplaceTransition, InplaceTransitionResult, MultiExpand, MultiOrder, NilNavigationAttribute, NotImplementedError, Order, OrderBase, Relation, RelationManager, Request, Response, RgxFixedTypeMapping, RgxTypeMapping, RgxTypeMapping1Par, RgxTypeMapping2Par, RubyStandardErrorException, Select, SelectBase, SequelAdapterError, SequelExceptionError, ServerApp, ServerError, ServerTransitionError, ServiceBase, ServiceMeta, ServiceOperationError, ServiceOperationParameterError, ServiceOperationParameterMissing, ServiceV1, ServiceV2, Transition, TransitionResult, TypeMapping, UnprocessableEntityError, UrlParameters4Coll, UrlParameters4Single, UrlParametersBase, VersionNotImplementedError, Walker

Constant Summary collapse

EMPTY_ARRAY =

frozen empty Array/Hash to reduce unncecessary object creation

[].freeze
EMPTY_HASH =
{}.freeze
EMPTY_HASH_IN_ARY =
[EMPTY_HASH].freeze
EMPTY_STRING =
''
ARY_204_EMPTY_HASH_ARY =
[204, EMPTY_HASH, EMPTY_ARRAY].freeze
SPACE =
' '
COMMA =
','
CONTENT_TYPE =

some prominent constants… probably already defined elsewhere eg in Rack but lets KISS

'content-type'
CONTENT_LENGTH =
'content-length'
LOCATION =
'location'
TEXTPLAIN_UTF8 =
'text/plain;charset=utf-8'
APPJSON =
'application/json'
APPXML =
'application/xml'
MP_MIXED =
'multipart/mixed'
APPXML_UTF8 =
'application/xml;charset=utf-8'
APPATOMXML_UTF8 =
'application/atomsvc+xml;charset=utf-8'
APPJSON_UTF8 =
'application/json;charset=utf-8'
CT_JSON =
{ CONTENT_TYPE => APPJSON_UTF8 }.freeze
CT_TEXT =
{ CONTENT_TYPE => TEXTPLAIN_UTF8 }.freeze
CT_ATOMXML =
{ CONTENT_TYPE => APPATOMXML_UTF8 }.freeze
CT_APPXML =
{ CONTENT_TYPE => APPXML_UTF8 }.freeze
METADATA_K =

these modules have all methods related to expand/defered output preparation and will be included in Service class

'__metadata'
EMPTYH =
{}.freeze
MAX_DATASERVICE_VERSION =
'2'
MIN_DATASERVICE_VERSION =
'1'
CV_MAX_DATASERVICE_VERSION =
Contract.valid(MAX_DATASERVICE_VERSION).freeze
CV_MIN_DATASERVICE_VERSION =
Contract.valid(MIN_DATASERVICE_VERSION).freeze
TRAILING_SLASH_RGX =
%r{/\z}.freeze
LEADING_SLASH_RGX =
%r{\A/}.freeze
GENERIC_415_RESP =
[415, {}, ['']].freeze
VERSION =
'0.8.2'
TransitionEnd =
Transition.new('\A(\/?)\z', trans: :transition_end)
TransitionExecuteFunc =
InplaceTransition.new(trans: :transition_execute_func)
TransitionMetadata =
Transition.new('\A(\/\$metadata)(.*)',
trans: :transition_metadata)
TransitionBatch =
Transition.new('\A(\/\$batch)(.*)',
trans: :transition_batch)
TransitionContentId =
Transition.new('\A(\/\$(\d+))(.*)',
trans: :transition_content_id,
remain_idx: 3)
TransitionCount =
Transition.new('(\A\/\$count)(.*)\z',
trans: :transition_count)
TransitionValue =
Transition.new('(\A\/\$value)(.*)\z',
trans: :transition_value)
Transition.new('(\A\/\$links)(.*)\z',
trans: :transition_links)
DB_TYPE_STRING_RGX =

TODO: use Sequel GENERIC_TYPES: –> Constants GENERIC_TYPES = %w’String Integer Float Numeric BigDecimal Date DateTime Time File TrueClass FalseClass’.freeze Classes specifying generic types that Sequel will convert to database-specific types.

/\ACHAR\s*\(\d+\)\z/.freeze
DB_TYPE_NUMDEC_RGX =
/\A(NUMERIC|DECIMAL)\s*(\(\s*((\d+)\s*(,\s*(\d+))?)\s*\))?\s*\z/i.freeze
DB_TYPE_GUID_RGX =

thank you rubular Test String: DECIMAL (55,2 ) Match groups 1 DECIMAL 2 (55,2 ) 3 55,2 4 55 5 ,2 6 2

/\A\s*(uuid)\s*\z/i.freeze
DB_TYPE_FLOATP_RGX =
/\A\s*(FLOAT)\s*(\(\s*(\d+)\s*\))?\s*\z/i.freeze
DB_TYPE_INTLIKE_RGX =

Note: “char” (quoted!) is postgresql’s byte type

/\A\s*(smallserial|smallint|integer|int2|int4|int8|int|mediumint|bigint|serial|bigserial|tinyint)\s*/i.freeze
RUBY_TY_EDM_TY_MAP =

type mappings are hard, especially between “Standards” like SQL and OData V2 (might be a bit better in V4 ?) this is all best effort/try to make it work logic

{ integer: 'Edm.Int32',
string: 'Edm.String',
date: 'Edm.DateTime',
datetime: 'Edm.DateTime',
time: 'Edm.Time',
boolean: 'Edm.Boolean',
float: 'Edm.Double',
decimal: 'Edm.Decimal',
blob: 'Edm.Binary' }.freeze
DB_TY_EDM_TY_MAP =
{ 'smallint' => 'Edm.Int16',
'int2' => 'Edm.Int16',
'smallserial' => 'Edm.Int16',
'int' => 'Edm.Int32',
'integer' => 'Edm.Int32',
'serial' => 'Edm.Int32',
'mediumint' => 'Edm.Int32',
'int4' => 'Edm.Int32',
'bigint' => 'Edm.Int64',
'bigserial' => 'Edm.Int64',
'int8' => 'Edm.Int64',
'tinyint' => 'Edm.Byte' }.freeze

Constants included from XMLNS

XMLNS::MSFT_ADO, XMLNS::MSFT_ADO_2007_EDMX, XMLNS::MSFT_ADO_2007_META, XMLNS::MSFT_ADO_2009_EDM, XMLNS::W3_2005_ATOM, XMLNS::W3_2007_APP

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Attribute Details

#allowed_transitionsObject

Returns the value of attribute allowed_transitions.



201
202
203
# File 'lib/odata/transition.rb', line 201

def allowed_transitions
  @allowed_transitions
end

Class Method Details

.add_edm_types(metadata, props) ⇒ Object



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/odata/edm/primitive_types.rb', line 63

def self.add_edm_types(, props)
  # try num/dec with db_type:
  [:edm_type] = if (md = DB_TYPE_NUMDEC_RGX.match(props[:db_type]))
                          prec = md[4]
                          scale = md[6]
                          if scale && prec
                            if scale == '0' # dont force default scale to 0 like SQL standard
                              [:edm_precision] = prec
                              "Edm.Decimal(#{prec})"
                            else
                              # we have precision and scale
                              [:edm_scale] = scale
                              [:edm_precision] = prec
                              "Edm.Decimal(#{prec},#{scale})"
                            end
                          elsif prec
                            # we have precision only
                            [:edm_precision] = prec
                            "Edm.Decimal(#{prec})"
                          else
                            'Edm.Decimal'
                          end
                        end
  return if [:edm_type]

  # try float(prec) with db_type:
  [:edm_type] = if (md = DB_TYPE_FLOATP_RGX.match(props[:db_type]))
                          # FLOAT( 22) match groups
                          # 1	FLOAT
                          # 2	(22 )
                          # 3	22

                          if (prec = md[3])
                            # we have precision only
                            [:edm_precision] = prec
                            'Edm.Double'
                          end
                        end
  return if [:edm_type]

  # try int-like with db_type:
  # smallint|int|integer|bigint|serial|bigserial

  [:edm_type] = if (md = DB_TYPE_INTLIKE_RGX.match(props[:db_type]))
                          if (itype = md[1])
                            DB_TY_EDM_TY_MAP[itype.downcase]
                          end
                        end
  return if [:edm_type]

  # try Guid with db_type:

  [:edm_type] = if DB_TYPE_GUID_RGX.match(props[:db_type])
                          'Edm.Guid'
                        end

  return if [:edm_type]

  # try with Sequel(ruby) type
  [:edm_type] = RUBY_TY_EDM_TY_MAP[props[:type]]
end

.ComplexType(**props) ⇒ Object



273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
# File 'lib/odata/complex_type.rb', line 273

def self.ComplexType(**props)
  Class.new(Safrano::ComplexType) do
    @props = props
    props.each do |a, _klassmod|
      asym = a.to_sym
      define_method(asym) { @values[asym] }
      define_method("#{a}=") { |val| @values[asym] = val }
    end
    define_method :initialize do |*p, **kwvals|
      super()
      p.zip(props.keys).each { |val, a| @values[a] = val } if p
      kwvals.each { |a, val| @values[a] = val if props.key?(a) } if kwvals
    end
  end
end

.create_nav_relation(child, assoc, parent) ⇒ Object

link newly created entities(child) to an existing parent by following the association_reflection rules



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/odata/navigation_attribute.rb', line 28

def self.create_nav_relation(child, assoc, parent)
  return unless assoc

  # Note: this coding shares some bits from our sequel/plugins/join_by_paths,
  # method build_unique_join_segments
  # eventually there is an opportunity to have more reusable code here
  case assoc[:type]
  when :one_to_many, :one_to_one
    # sets the FK values in child to corresponding related parent key-values
    # thus creating the "link" between the new entity and the parent
    # if a FK value is already set (not nil/NULL) then only check the
    # consistency with the corresponding parent key-value
    # If the FK value and the parent key value are different, then it's  a
    # a Bad Request error

    leftm = assoc[:model] # should be same as parent.class
    lks = [leftm.primary_key].flatten
    rks = [assoc[:key]].flatten
    join_cond = rks.zip(lks).to_h
    join_cond.each do |rk, lk|
      if child.values[rk] # FK in new entity from payload not nil, only check consistency
        # with the parent - id(s)
        # if (child.values[rk] != parent.pk_hash[lk]) # error...
        # TODO
        # end
      else # we can set the FK value, thus creating the "link"
        child.set(rk => parent.pk_hash[lk])
      end
    end
  when :many_to_one
    # sets the FK values in parent to corresponding related child key-values
    # thus creating the "link" between the new entity and the parent
    # Per design, this can only be called when the FK value is nil
    # from NilNavigationAttribute.odata_post
    lks = [assoc[:key]].flatten
    rks = [child.class.primary_key].flatten
    join_cond = rks.zip(lks).to_h
    join_cond.each do |rk, lk|
      if parent.values[lk] # FK in parent not nil, only check consistency
        # with the child - id(s)
        # if parent.values[lk] != child.pk_hash[rk] # error...
        # TODO
        # end
      else # we can set the FK value, thus creating the "link"
        parent.set(lk => child.pk_hash[rk])
      end
    end
  end
end

.FunctionImport(name) ⇒ Object



8
9
10
# File 'lib/odata/function_import.rb', line 8

def self.FunctionImport(name)
  FunctionImport::Function.new(name)
end

.remove_nav_relation(assoc, parent) ⇒ Object

remove the relation between entity and parent by clearing the FK field(s) (if allowed)



10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# File 'lib/odata/navigation_attribute.rb', line 10

def self.remove_nav_relation(assoc, parent)
  return unless assoc

  return unless assoc[:type] == :many_to_one

  # removes/clear the FK values in parent
  # thus deleting the "link" between the entity and the parent
  # Note: This is called if we have to delete the child--> can only be
  # done after removing the FK in parent (if allowed!)
  lks = [assoc[:key]].flatten
  lks.each do |lk|
    parent.set(lk => nil)
    parent.save(transaction: false)
  end
end

.with_error(result) {|err| ... } ⇒ Object

used in function import error handling. cf. func import / do_execute_func

Yields:

  • (err)


60
61
62
63
64
# File 'lib/odata/error.rb', line 60

def self.with_error(result)
  return unless result.respond_to?(:error) && (err = result.error)

  yield err
end