eco-helpers 2.0.16 → 2.0.22

Sign up to get free protection for your applications and to get access to all the features.
Files changed (98) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +125 -6
  3. data/eco-helpers.gemspec +10 -5
  4. data/lib/eco-helpers.rb +2 -0
  5. data/lib/eco/api/common/base_loader.rb +18 -0
  6. data/lib/eco/api/common/loaders/parser.rb +1 -0
  7. data/lib/eco/api/common/people/default_parsers.rb +1 -0
  8. data/lib/eco/api/common/people/default_parsers/date_parser.rb +11 -1
  9. data/lib/eco/api/common/people/default_parsers/login_providers_parser.rb +1 -1
  10. data/lib/eco/api/common/people/default_parsers/policy_groups_parser.rb +11 -11
  11. data/lib/eco/api/common/people/default_parsers/xls_parser.rb +53 -0
  12. data/lib/eco/api/common/people/entries.rb +1 -0
  13. data/lib/eco/api/common/people/entry_factory.rb +88 -23
  14. data/lib/eco/api/common/people/person_entry.rb +5 -2
  15. data/lib/eco/api/common/people/person_parser.rb +1 -1
  16. data/lib/eco/api/common/session.rb +1 -0
  17. data/lib/eco/api/common/session/base_session.rb +2 -0
  18. data/lib/eco/api/common/session/helpers.rb +30 -0
  19. data/lib/eco/api/common/session/helpers/prompt_user.rb +34 -0
  20. data/lib/eco/api/common/session/mailer.rb +0 -1
  21. data/lib/eco/api/common/session/s3_uploader.rb +0 -1
  22. data/lib/eco/api/common/session/sftp.rb +0 -1
  23. data/lib/eco/api/common/version_patches/ecoportal_api/external_person.rb +1 -1
  24. data/lib/eco/api/common/version_patches/ecoportal_api/internal_person.rb +7 -4
  25. data/lib/eco/api/common/version_patches/exception.rb +11 -4
  26. data/lib/eco/api/microcases.rb +3 -1
  27. data/lib/eco/api/microcases/append_usergroups.rb +0 -1
  28. data/lib/eco/api/microcases/people_cache.rb +2 -2
  29. data/lib/eco/api/microcases/people_load.rb +2 -2
  30. data/lib/eco/api/microcases/people_refresh.rb +2 -2
  31. data/lib/eco/api/microcases/people_search.rb +6 -6
  32. data/lib/eco/api/microcases/preserve_default_tag.rb +23 -0
  33. data/lib/eco/api/microcases/preserve_filter_tags.rb +28 -0
  34. data/lib/eco/api/microcases/preserve_policy_groups.rb +30 -0
  35. data/lib/eco/api/microcases/set_account.rb +0 -1
  36. data/lib/eco/api/microcases/with_each.rb +67 -6
  37. data/lib/eco/api/microcases/with_each_present.rb +4 -2
  38. data/lib/eco/api/microcases/with_each_starter.rb +4 -2
  39. data/lib/eco/api/organization.rb +1 -0
  40. data/lib/eco/api/organization/people.rb +98 -22
  41. data/lib/eco/api/organization/people_similarity.rb +272 -0
  42. data/lib/eco/api/organization/person_schemas.rb +5 -1
  43. data/lib/eco/api/organization/policy_groups.rb +5 -1
  44. data/lib/eco/api/organization/presets_factory.rb +22 -83
  45. data/lib/eco/api/organization/presets_integrity.json +6 -0
  46. data/lib/eco/api/organization/presets_values.json +5 -4
  47. data/lib/eco/api/organization/tag_tree.rb +33 -0
  48. data/lib/eco/api/policies/default_policies/99_user_access_policy.rb +0 -30
  49. data/lib/eco/api/session.rb +20 -28
  50. data/lib/eco/api/session/batch.rb +25 -7
  51. data/lib/eco/api/session/config.rb +0 -10
  52. data/lib/eco/api/session/config/apis.rb +80 -14
  53. data/lib/eco/api/session/config/people.rb +1 -17
  54. data/lib/eco/api/usecases.rb +2 -2
  55. data/lib/eco/api/usecases/base_case.rb +2 -2
  56. data/lib/eco/api/usecases/base_io.rb +17 -4
  57. data/lib/eco/api/usecases/default_cases.rb +2 -1
  58. data/lib/eco/api/usecases/default_cases/abstract_policygroup_abilities_case.rb +4 -4
  59. data/lib/eco/api/usecases/default_cases/analyse_people_case.rb +223 -0
  60. data/lib/eco/api/usecases/default_cases/clean_unknown_tags_case.rb +37 -0
  61. data/lib/eco/api/usecases/default_cases/codes_to_tags_case.rb +2 -3
  62. data/lib/eco/api/usecases/default_cases/reset_landing_page_case.rb +11 -1
  63. data/lib/eco/api/usecases/default_cases/restore_db_case.rb +1 -2
  64. data/lib/eco/api/usecases/default_cases/supers_cyclic_identify_case.rb +1 -1
  65. data/lib/eco/api/usecases/default_cases/supers_hierarchy_case.rb +1 -1
  66. data/lib/eco/api/usecases/default_cases/to_csv_case.rb +132 -29
  67. data/lib/eco/api/usecases/default_cases/to_csv_detailed_case.rb +61 -36
  68. data/lib/eco/api/usecases/ooze_samples/ooze_update_case.rb +3 -2
  69. data/lib/eco/cli/config/default/input.rb +61 -8
  70. data/lib/eco/cli/config/default/options.rb +48 -17
  71. data/lib/eco/cli/config/default/people.rb +18 -24
  72. data/lib/eco/cli/config/default/people_filters.rb +3 -3
  73. data/lib/eco/cli/config/default/usecases.rb +97 -32
  74. data/lib/eco/cli/config/default/workflow.rb +22 -13
  75. data/lib/eco/cli/config/help.rb +1 -0
  76. data/lib/eco/cli/config/options_set.rb +106 -13
  77. data/lib/eco/cli/config/use_cases.rb +33 -33
  78. data/lib/eco/cli/scripting/args_helpers.rb +32 -5
  79. data/lib/eco/csv.rb +4 -2
  80. data/lib/eco/csv/table.rb +121 -21
  81. data/lib/eco/data.rb +1 -0
  82. data/lib/eco/data/crypto/encryption.rb +3 -3
  83. data/lib/eco/data/files/helpers.rb +6 -4
  84. data/lib/eco/data/fuzzy_match.rb +201 -0
  85. data/lib/eco/data/fuzzy_match/array_helpers.rb +75 -0
  86. data/lib/eco/data/fuzzy_match/chars_position_score.rb +38 -0
  87. data/lib/eco/data/fuzzy_match/ngrams_score.rb +82 -0
  88. data/lib/eco/data/fuzzy_match/pairing.rb +95 -0
  89. data/lib/eco/data/fuzzy_match/result.rb +87 -0
  90. data/lib/eco/data/fuzzy_match/results.rb +77 -0
  91. data/lib/eco/data/fuzzy_match/score.rb +49 -0
  92. data/lib/eco/data/fuzzy_match/stop_words.rb +35 -0
  93. data/lib/eco/data/fuzzy_match/string_helpers.rb +82 -0
  94. data/lib/eco/version.rb +1 -1
  95. metadata +147 -11
  96. data/lib/eco/api/microcases/refresh_abilities.rb +0 -19
  97. data/lib/eco/api/organization/presets_reference.json +0 -59
  98. data/lib/eco/api/usecases/default_cases/refresh_abilities_case.rb +0 -30
@@ -28,7 +28,11 @@ module Eco
28
28
  end
29
29
 
30
30
  def schema(id_name)
31
- @by_id.fetch(schema_id(id_name), nil)
31
+ self[id_name]
32
+ end
33
+
34
+ def [](id_name)
35
+ @by_id[schema_id(id_name)]
32
36
  end
33
37
 
34
38
  private
@@ -44,7 +44,11 @@ module Eco
44
44
  end
45
45
 
46
46
  def policy_group(id_name)
47
- @by_id.fetch(policy_group_id(id_name), nil)
47
+ self[id_name]
48
+ end
49
+
50
+ def [](id_name)
51
+ @by_id[policy_group_id(id_name)]
48
52
  end
49
53
 
50
54
  def user_pg_ids(initial: [], final: [], non_custom: (non_custom_not_used = true; []), preserve_custom: true)
@@ -3,6 +3,8 @@ module Eco
3
3
  module Organization
4
4
 
5
5
  class PresetsFactory
6
+ ABILITIES = File.join(__dir__, 'presets_values.json')
7
+ INTEGRITY = File.join(__dir__, 'presets_integrity.json')
6
8
 
7
9
  class << self
8
10
 
@@ -24,44 +26,36 @@ module Eco
24
26
 
25
27
  end
26
28
 
27
- ABILITIES = File.join(__dir__, 'presets_values.json')
28
- INTEGRITY = File.join(__dir__, 'presets_integrity.json')
29
- DEFAULT_CUSTOM = 'presets_custom.json'
30
- DEFAULT_MAP = 'presets_map.json'
31
-
32
- def initialize(presets_custom: DEFAULT_CUSTOM, presets_map: DEFAULT_MAP, enviro: nil, policy_groups: nil)
29
+ def initialize(enviro: nil, policy_groups: nil)
33
30
  fatal("Expecting Environment object. Given: #{enviro}") if enviro && !enviro.is_a?(Eco::API::Common::Session::Environment)
34
31
  @enviro = enviro
35
-
36
- @policy_groups = policy_groups
37
- @presets_custom_file = presets_custom || DEFAULT_CUSTOM
38
- @presets_map_file = presets_map || DEFAULT_MAP
32
+ @policy_groups = policy_groups
39
33
  end
40
34
 
41
- def new(*policy_group_ids_or_names)
42
-
43
- names = policy_group_ids_or_names.map do |id_name|
44
- policy_groups.to_name(id_name)&.downcase
45
- end.compact
35
+ # @return [Array<String>] all the abilities
36
+ def keys
37
+ self.class.abilities
38
+ end
46
39
 
47
- if presets_map
48
- preset_names = names.map { |name| presets_map.fetch(name, nil) }
49
- else # option to do not use preset mapping (so just the policy group name)
50
- preset_names = names
51
- end
52
- compile(*preset_names)
40
+ def valid?(preset)
41
+ validate(perset).length == 0
53
42
  end
54
43
 
55
- # @return [Array<String>] all the abilities
56
- def keys
57
- abilities_model.keys
44
+ def validate(preset)
45
+ [].tap do |errors|
46
+ if err = preset_errors(preset)
47
+ errors << "{ '#{key}' preset -> #{err}}"
48
+ end
49
+ if err = preset_integrity(preset)
50
+ errors << "{ '#{key}' preset -> #{err}}"
51
+ end
52
+ end
58
53
  end
59
54
 
60
55
  private
61
56
 
62
- def compile(*preset_names)
63
- fatal("You need to specify an existing file for the custom presets.") if !@presets_custom
64
- @presets_custom.values_at(*preset_names).compact.reduce({}) do |p1, p2|
57
+ def compile(*presets)
58
+ presets.compact.reduce({}) do |p1, p2|
65
59
  merge(p1, p2)
66
60
  end
67
61
  end
@@ -79,11 +73,6 @@ module Eco
79
73
  end
80
74
  end
81
75
 
82
- # unsused: only play with the given abilities
83
- def empty_model
84
- JSON.parse(abilities_model.to_json).transform_values {|v| nil }
85
- end
86
-
87
76
  def preset_errors(preset)
88
77
  return "No preset given" if !preset
89
78
  errors = preset.map do |k, v|
@@ -146,7 +135,7 @@ module Eco
146
135
  end
147
136
 
148
137
  def ability_value_idx(ability, value)
149
- abilities_model[ability].index(value)
138
+ abilities_model[ability].index(value) || -1
150
139
  end
151
140
 
152
141
  def abilities_model
@@ -163,56 +152,6 @@ module Eco
163
152
  @policy_groups
164
153
  end
165
154
 
166
- def presets_custom
167
- return @presets_custom if instance_variable_defined?(:@presets_custom)
168
- @presets_custom = nil
169
- if @presets_custom_file
170
- if (file = File.expand_path(@presets_custom_file)) && File.exists?(file)
171
- @presets_custom = JSON.load(File.open(file)).tap do |custom_presets|
172
- errors = custom_presets.each_with_object([]) do |(key, preset), errors|
173
- if err = preset_errors(preset)
174
- errors << "{ '#{key}' preset -> #{err}}"
175
- end
176
- if err = preset_integrity(preset)
177
- errors << "{ '#{key}' preset -> #{err}}"
178
- end
179
- end
180
-
181
- fatal("File '#{file}' contains invalid presets:\n #{errors.join("\n ")}") if errors.length > 0
182
- end
183
- end
184
- end
185
- end
186
-
187
- def presets_map
188
- return @presets_map if instance_variable_defined?(:@presets_map)
189
- @presets_map = nil
190
- if @presets_map_file
191
- if (file = File.expand_path(@presets_map_file)) && File.exists?(file)
192
- fatal("Maps file specified without 'presets_custom.json' file. Aborting!") if !presets_custom
193
- @presets_map = JSON.load(File.open(file)).tap do |map_presets|
194
-
195
- errors = []
196
- if policy_groups.length > 0
197
- errors = policy_groups.map do |pg|
198
- exists = map_presets[pg.name.downcase] || presets_custom[pg.name.downcase]
199
- exists ? nil : "'#{pg.name}'"
200
- end.compact
201
-
202
- warn("No maps or no preset for policy group(s): #{errors.join(", ")}") if errors.length > 0
203
- end
204
-
205
- errors = map_presets.map do |source, dest|
206
- presets_custom[dest] ? nil : "'#{dest}'"
207
- end.compact
208
-
209
- warn("Unexisting mapped preset(s): #{errors.uniq.join(", ")}") if errors.length > 0
210
-
211
- end
212
- end
213
- end
214
- end
215
-
216
155
  def fatal(msg)
217
156
  raise msg if !@enviro
218
157
  @enviro.logger.fatal(msg)
@@ -48,5 +48,11 @@
48
48
  "at_least": {"person_core_edit": "edit"}
49
49
  }
50
50
  }
51
+ ],
52
+ "person_abilities": [
53
+ { "value": "view", "conditions": {
54
+ "at_least": {"person_core_edit": "edit"}
55
+ }
56
+ }
51
57
  ]
52
58
  }
@@ -2,14 +2,15 @@
2
2
  "files": [null, "download", "upload", "browse", "administrate"],
3
3
  "data": [null, "view", "update", "administrate", "implement"],
4
4
  "reports": [null, "view", "edit", "administrate"],
5
+ "pages": [null, "view", "update", "create", "administrate"],
6
+ "page_editor": [null, "basic", "intermediate", "advanced", "implement"],
7
+ "registers": [null, "view", "dashboard", "administrate", "implement"],
5
8
  "tasks": [null, "reassign_self", "reassign", "administrate"],
6
9
  "organization": [null, "view", "administrate", "implement"],
7
10
  "person_core": [null, "attach", "view_people_manager", "dashboard"],
8
11
  "person_core_create": [null, "create"],
9
12
  "person_core_edit": [null, "edit"],
10
- "person_account": [null, "view", "create", "edit"],
11
13
  "person_details": [null, "view", "edit_public", "view_private", "edit_private"],
12
- "pages": [null, "view", "update", "create", "administrate"],
13
- "page_editor": [null, "basic", "intermediate", "advanced", "implement"],
14
- "registers": [null, "view", "dashboard", "administrate", "implement"]
14
+ "person_account": [null, "view", "create", "edit"],
15
+ "person_abilities": [null, "view", "edit"]
15
16
  }
@@ -42,6 +42,39 @@ module Eco
42
42
  init_hashes
43
43
  end
44
44
 
45
+ # Updates the tag of the current tree
46
+ def tag=(value)
47
+ @tag = value
48
+ end
49
+
50
+ # @return [Eco::API::Organization::TagTree]
51
+ def dup
52
+ self.class.new(as_json)
53
+ end
54
+
55
+ # @return [Array] with the differences
56
+ def diff(tagtree, differences: {}, level: 0, **options)
57
+ require 'hashdiff'
58
+ Hashdiff.diff(self.as_json, tagtree.as_json, **options.slice(:array_path, :similarity, :use_lcs))
59
+ end
60
+
61
+ def top?
62
+ depth == -1
63
+ end
64
+
65
+ # @return [Array[Hash]] where `Hash` is a `node` `{"tag" => TAG, "nodes": Array[Hash]}`
66
+ def as_json
67
+ nodes_json = nodes.map {|node| node.as_json}
68
+ if top?
69
+ nodes_json
70
+ else
71
+ {
72
+ "tag" => tag,
73
+ "nodes" => nodes_json
74
+ }
75
+ end
76
+ end
77
+
45
78
  # @return [Boolean] `true` if there are tags in the node, `false` otherwise.
46
79
  def empty?
47
80
  @has_tags.empty?
@@ -12,7 +12,6 @@ class Eco::API::Policies::DefaultPolicies::UserAccess < Eco::API::Common::Loader
12
12
  people.each do |person|
13
13
  remove_account_when_no_email!(person) if person.email.to_s.empty?
14
14
  person.account.policy_group_ids = defid if no_policy_group_ids?(person)
15
- refresh_abilities!(person)
16
15
  end
17
16
 
18
17
  warn_account_removal!
@@ -40,39 +39,10 @@ class Eco::API::Policies::DefaultPolicies::UserAccess < Eco::API::Common::Loader
40
39
  return !!person.original_doc["account"]
41
40
  end
42
41
 
43
- def refresh_abilities!(person)
44
- return nil if options.dig(:exclude, :abilities)
45
- return nil unless account = person.account
46
- account.permissions_custom = session.new_preset(person)
47
- account.permissions_custom = min_abilities if no_abilities?(person)
48
- end
49
-
50
42
  def no_policy_group_ids?(person)
51
43
  (account = person.account) && account.policy_group_ids.empty?
52
44
  end
53
45
 
54
- def no_abilities?(person)
55
- return true unless account = person.account
56
- account.permissions_custom && account.permissions_custom.values.all?(&:nil?)
57
- end
58
-
59
- def min_abilities
60
- {
61
- "files" => "upload",
62
- "data" => nil,
63
- "reports" => nil,
64
- "pages" => "create",
65
- "page_editor" => "basic",
66
- "registers" => "view",
67
- "organization" => nil,
68
- "person_core" => "attach",
69
- "person_core_edit" => nil,
70
- "person_core_create" => nil,
71
- "person_details" => "view",
72
- "person_account" => nil
73
- }
74
- end
75
-
76
46
  def defid
77
47
  @defid ||= policy_groups.to_id([default_group]).compact
78
48
  end
@@ -61,28 +61,19 @@ module Eco
61
61
  self
62
62
  end
63
63
 
64
- # Builds the presets using the usergroup ids of the input.
65
- # @note for each flag/ability it will take the highest among those mapped for the present usergroups.
66
- # @param input [Ecoportal::API::Internal::Person, Array<String>] the array should be of usegroup names or ids.
67
- # @return [Hash] with custom presets.
68
- def new_preset(input)
69
- case input
70
- when Ecoportal::API::Internal::Person
71
- presets_factory.new(*input&.account&.policy_group_ids)
72
- when Array
73
- presets_factory.new(*input)
74
- else
75
- presets_factory.new(input)
76
- end
77
- end
78
-
79
64
  # Helper to state the abilities that a person should have with given their usergroups
80
65
  def presets_factory
81
- @presets_factory ||= Eco::API::Organization::PresetsFactory.new({
82
- presets_custom: file_manager.dir.file(config.people.presets_custom, should_exist: true),
83
- presets_map: file_manager.dir.file(config.people.presets_map, should_exist: true),
84
- enviro: enviro
85
- })
66
+ @presets_factory ||= Eco::API::Organization::PresetsFactory.new(enviro: enviro)
67
+ end
68
+
69
+ # @return [Eco::Data::Mapper] the mappings between the internal and external attribute/property names.
70
+ def fields_mapper
71
+ return @fields_mapper if instance_variable_defined?(:@fields_mapper)
72
+ mappings = []
73
+ if map_file = config.people.fields_mapper
74
+ mappings = map_file ? file_manager.load_json(map_file) : []
75
+ end
76
+ @fields_mapper = Eco::Data::Mapper.new(mappings)
86
77
  end
87
78
 
88
79
  # Helper to obtain a EntryFactory
@@ -93,17 +84,16 @@ module Eco
93
84
  def entry_factory(schema: nil)
94
85
  schema = to_schema(schema) || self.schema
95
86
  return @entry_factories[schema&.id] if @entry_factories.key?(schema&.id)
96
-
97
- mappings = []
98
- if map_file = config.people.fields_mapper
99
- mappings = map_file ? file_manager.load_json(map_file) : []
87
+ unless @entry_factories.empty?
88
+ @entry_factories[schema&.id] = @entry_factories.values.first.newFactory(schema: schema)
89
+ return @entry_factories[schema&.id]
100
90
  end
101
91
 
102
92
  @entry_factories[schema&.id] = Eco::API::Common::People::EntryFactory.new(
103
93
  enviro,
104
94
  schema: schema,
105
95
  person_parser: config.people.parser,
106
- attr_map: Eco::Data::Mapper.new(mappings)
96
+ attr_map: fields_mapper
107
97
  )
108
98
  end
109
99
 
@@ -122,11 +112,13 @@ module Eco
122
112
  # @param attr [String] type (`Symbol`) or attribute (`String`) to target a specific parser.
123
113
  # @param source [Any] source value to be parsed.
124
114
  # @param phase [Symbol] the phase when this parser should be active.
125
- def parse_attribute(attr, source, phase = :internal)
115
+ # @param phase [Symbol] the phase when this parser should be active.
116
+ # @return [Object] the parsed attribute.
117
+ def parse_attribute(attr, source, phase = :internal, deps: {})
126
118
  unless parsers = entry_factory.person_parser
127
119
  raise "There are no parsers defined"
128
120
  end
129
- parsers.parse(attr, source, phase)
121
+ parsers.parse(attr, source, phase, deps: deps)
130
122
  end
131
123
 
132
124
  # @see Eco::API::Common::People::EntryFactory#export
@@ -146,7 +138,7 @@ module Eco
146
138
  # @see Eco::API::Common::People::EntryFactory#new
147
139
  # @return [Eco::API::Common::People::PersonEntry] parsed entry.
148
140
  def new_entry(data, dependencies: {})
149
- entry_factory.new(data, dependencies: dependencies)
141
+ entry_factory(schema: data&.details&.schema_id).new(data, dependencies: dependencies)
150
142
  end
151
143
 
152
144
  # @see Eco::API::Common::People::EntryFactory#entries
@@ -114,14 +114,16 @@ module Eco
114
114
  logger.info(msg) unless silent
115
115
 
116
116
  start_slice = Time.now
117
- people_api.batch do |batch|
118
- slice.each do |person|
119
- batch.public_send(method, person) do |response|
120
- faltal("Request with no response") unless !!response
121
- status[person] = response
117
+ offer_retry_on(Ecoportal::API::Errors::TimeOut) do
118
+ people_api.batch do |batch|
119
+ slice.each do |person|
120
+ batch.public_send(method, person) do |response|
121
+ faltal("Request with no response") unless !!response
122
+ status[person] = response
123
+ end
122
124
  end
123
- end
124
- end # next batch
125
+ end # end batch
126
+ end
125
127
 
126
128
  iteration += 1
127
129
  done += slice.length
@@ -129,6 +131,22 @@ module Eco
129
131
  end
130
132
  end
131
133
 
134
+ def offer_retry_on(error_type, retries_left = 3, &block)
135
+ begin
136
+ block.call
137
+ rescue error_type => e
138
+ raise unless retries_left > 0
139
+ explanation = "Batch TimeOut. You have #{retries_left} retries left."
140
+ prompt_user("Do you want to retry (y/N)?", explanation, default: "Y", timeout: 10) do |response|
141
+ if response.upcase.start_with?("Y")
142
+ offer_retry_on(error_type, retries_left - 1, &block)
143
+ else
144
+ raise
145
+ end
146
+ end
147
+ end
148
+ end
149
+
132
150
  def str_stats(start, count)
133
151
  now = Time.now
134
152
  secs = (now - start).round(3)
@@ -297,16 +297,6 @@ module Eco
297
297
  people.default_schema = name
298
298
  end
299
299
 
300
- # Specify the file with the account custom abilities presets
301
- def presets_custom=(file)
302
- people.presets_custom = file
303
- end
304
-
305
- # Specify the file with the usergroup to custom presets mapping
306
- def presets_map=(file)
307
- people.presets_map = file
308
- end
309
-
310
300
  # @see Eco::API::Session::Config::People
311
301
  # @param (see Eco::API::Session::Config::People)
312
302
  # @return [Eco::API::Common::People::PersonParser] parser/serializer for the defined `format`.