goa_model_gen 0.5.0 → 0.6.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e0cacd5ba7fc52902ba68ae788e6e4ec5a50fb6dd663da221d995c81b425f560
4
- data.tar.gz: 71368a7fdecfbdac2cc28168df06f583891833f8067c662323576ff58060fd87
3
+ metadata.gz: 82c7930eb91dbfa049e61b483ab4e570e5319020a1a0e283bf79c9014b7059f3
4
+ data.tar.gz: b7dcdd494bbfaf4f93333398c285f94b31e47d24b2c5adb643cfadb844b2c29d
5
5
  SHA512:
6
- metadata.gz: 70e0595b798469b555cf54c3fd277c5768a4ca3972a1654ec345c3d95d68cee0657fea988e43e27a8aed168ca7e360fb840a8ca8a3ca2f14aa23d81ffd74b649
7
- data.tar.gz: 9d3390c47308bada677ce7ed720fa39984a86013a49d0c0c010cec95c7e77c41ace145137d988f80206d32a814e0f433bce9ac971b918135e05893c93a3985bd
6
+ metadata.gz: 873ab6ec5df1fd224c8c07d47ca1b4d77220f9aa16cb67913fede6726f4097bd8dc2682afbff38bc6dfbb8a8ce1b5fc8a637066b67233dc9850ff3f7fc246f4e
7
+ data.tar.gz: 682549fb13dc19eb00d4c7a82be17b8ede6a6e1e8cf7c081ccff9dd52883923b2fe4e7cd6524c0c72a77b7e9ae7b3096b901387805bbcbaf2e413212733cee55
@@ -10,6 +10,8 @@ require "goa_model_gen/generator"
10
10
  module GoaModelGen
11
11
  class Cli < Thor
12
12
  class_option :version, type: :boolean, aliases: 'v', desc: 'Show version before processing'
13
+ class_option :dryrun, type: :boolean, aliases: 'd', desc: "Don't write or overwrite file"
14
+ class_option :force, type: :boolean, aliases: 'f', desc: 'Force overwrite files'
13
15
  class_option :log_level, type: :string, aliases: 'l', desc: 'Log level, one of debug,info,warn,error,fatal. The default value is info'
14
16
  class_option :config, type: :string, aliases: 'c', default: './goa_model_gen.yaml', desc: 'Path to config file. You can generate it by config subcommand'
15
17
 
@@ -19,18 +21,6 @@ module GoaModelGen
19
21
  open(path, 'w'){|f| f.puts(Config.new.fulfill.to_yaml) }
20
22
  end
21
23
 
22
- desc "bootstrap", "Generate files not concerned with model"
23
- def bootstrap
24
- setup
25
- generator = new_generator
26
- {
27
- "templates/goon.go.erb" => "model/goon.go",
28
- "templates/converter_base.go.erb" => "controller/converter_base.go",
29
- }.each do |template, dest|
30
- generator.run(template, dest, overwrite: true)
31
- end
32
- end
33
-
34
24
  desc "show FILE1...", "Show model info from definition files"
35
25
  def show(*paths)
36
26
  setup
@@ -43,27 +33,30 @@ module GoaModelGen
43
33
  desc "model FILE1...", "Generate model files from definition files"
44
34
  def model(*paths)
45
35
  setup
36
+ new_generator.process({
37
+ "templates/goon.go.erb" => File.join(cfg.model_dir, "goon.go"),
38
+ 'templates/validator.go.erb' => File.join(cfg.model_dir, 'validator.go'),
39
+ })
46
40
  load_types_for(paths) do |source_file|
47
- generator = new_generator.tap{|g| g.source_file = source_file }
48
- [
49
- {path: 'templates/model.go.erb', suffix: '.go', overwrite: true},
50
- {path: 'templates/model_validation.go.erb', suffix: '_validation.go', overwrite: false},
51
- ].each do |d|
52
- dest = File.join(cfg.model_dir, File.basename(source_file.yaml_path, ".*") + d[:suffix])
53
- generator.run(d[:path], dest, overwrite: d[:overwrite])
54
- end
41
+ new_generator.tap{|g| g.source_file = source_file }.process({
42
+ 'templates/model.go.erb' => dest_path(cfg.model_dir, source_file, '.go'),
43
+ 'templates/model_store.go.erb' => dest_path(cfg.model_dir, source_file, '_store.go'),
44
+ 'templates/model_validation.go.erb' => dest_path(cfg.model_dir, source_file, '_validation.go'),
45
+ })
55
46
  end
56
47
  end
57
48
 
58
49
  desc "converter FILE1...", "Generate converter files from definition files and swagger.yaml"
59
50
  def converter(*paths)
60
51
  setup
52
+ new_generator.process({
53
+ "templates/converter_base.go.erb" => File.join(cfg.controller_dir, "converter_base.go"),
54
+ })
61
55
  load_types_for(paths) do |source_file|
62
- generator = new_generator.tap{|g| g.source_file = source_file }
63
- dest = File.join(cfg.controller_dir, File.basename(source_file.yaml_path, ".*") + "_conv.go")
64
- if source_file.types.any?{|t| !!t.payload || !!t.media_type}
65
- generator.run('templates/converter.go.erb', dest, overwrite: true)
66
- end
56
+ next if source_file.types.all?{|t| !t.payload && !t.media_type}
57
+ new_generator.tap{|g| g.source_file = source_file }.process({
58
+ 'templates/converter.go.erb' => dest_path(cfg.controller_dir, source_file, "_conv.go"),
59
+ })
67
60
  end
68
61
  end
69
62
 
@@ -87,7 +80,10 @@ module GoaModelGen
87
80
  end
88
81
 
89
82
  def new_generator
90
- GoaModelGen::Generator.new(cfg)
83
+ GoaModelGen::Generator.new(cfg).tap do |g|
84
+ g.force = options[:force]
85
+ g.dryrun = options[:dryrun]
86
+ end
91
87
  end
92
88
 
93
89
  def load_types_for(paths)
@@ -97,6 +93,10 @@ module GoaModelGen
97
93
  yield(source_file)
98
94
  end
99
95
  end
96
+
97
+ def dest_path(dir, source_file, suffix)
98
+ File.join(dir, File.basename(source_file.yaml_path, ".*") + suffix)
99
+ end
100
100
  end
101
101
 
102
102
  end
@@ -15,6 +15,7 @@ module GoaModelGen
15
15
  gofmt_disabled
16
16
  model_dir
17
17
  controller_dir
18
+ validator_path
18
19
  ].freeze
19
20
 
20
21
  attr_accessor *ATTRIBUTES
@@ -25,6 +26,7 @@ module GoaModelGen
25
26
  @gofmt_disabled ||= false
26
27
  @model_dir ||= "./model"
27
28
  @controller_dir ||= "./controller"
29
+ @validator_path ||= "gopkg.in/go-playground/validator.v9"
28
30
  self
29
31
  end
30
32
 
@@ -9,6 +9,7 @@ module GoaModelGen
9
9
  attr_reader :name, :type, :default
10
10
  attr_accessor :format # for swagger. See https://swagger.io/docs/specification/data-models/data-types/
11
11
  attr_accessor :required
12
+ attr_accessor :unique
12
13
  attr_accessor :validation
13
14
  attr_accessor :swagger_name
14
15
  attr_reader :type_obj
@@ -19,6 +20,7 @@ module GoaModelGen
19
20
  @type = attrs['type']
20
21
  @format = attrs['format']
21
22
  @required = attrs['required']
23
+ @unique = attrs['unique']
22
24
  @default = attrs['default']
23
25
  @validation = attrs['validation']
24
26
  @goa_name = attrs['goa_name']
@@ -49,6 +51,10 @@ module GoaModelGen
49
51
  !required
50
52
  end
51
53
 
54
+ def unique?
55
+ !!unique
56
+ end
57
+
52
58
  def not_null?
53
59
  required || !default.nil?
54
60
  end
@@ -11,9 +11,13 @@ module GoaModelGen
11
11
  # These are used in templates
12
12
  attr_reader :config
13
13
  attr_accessor :source_file
14
+ attr_accessor :force, :dryrun
14
15
 
15
16
  def initialize(config)
16
17
  @config = config
18
+ @user_editable = false
19
+ @force = false
20
+ @dryrun = false
17
21
  end
18
22
 
19
23
  def golang_helper
@@ -42,6 +46,13 @@ module GoaModelGen
42
46
  end
43
47
  end
44
48
 
49
+ def user_editable(value: true)
50
+ @user_editable = value
51
+ end
52
+ def user_editable?
53
+ @user_editable
54
+ end
55
+
45
56
  GO_BASE_PATH = File.expand_path('../templates/base.go.erb', __FILE__)
46
57
 
47
58
  PACKAGE_FOR_IMPORT = {
@@ -63,13 +74,36 @@ module GoaModelGen
63
74
  base.result(binding).strip
64
75
  end
65
76
 
66
- def run(template_path, output_path, overwrite: false)
67
- return if File.exist?(output_path) && !overwrite
77
+ COLORS = {
78
+ generate: "\e[32m", # green # !file_exist
79
+ no_change: "\e[37m", # white # file_exist && !modified
80
+ overwrite: "\e[33m", # yellow # file_exist && !user_editable
81
+ keep: "\e[34m", # blue # file_exist && user_editable && !force
82
+ force_overwrite: "\e[31m", # red # file_exist && user_editable && force
83
+ clear: "\e[0m", # clear
84
+ }
85
+ MAX_ACTION_LENGTH = COLORS.keys.map(&:to_s).map(&:length).max
86
+
87
+ def run(template_path, output_path)
88
+ already_exist = File.exist?(output_path)
68
89
  content = generate(template_path)
90
+ modified = already_exist ? (content != File.read(output_path)) : true
91
+ action =
92
+ !already_exist ? :generate :
93
+ !modified ? :no_change :
94
+ !user_editable? ? :overwrite :
95
+ force ? :force_overwrite : :keep
96
+ GoaModelGen.logger.info("%s%-#{MAX_ACTION_LENGTH}s %s%s" % [COLORS[action], action.to_s, output_path, COLORS[:clear]])
97
+ return if action == :no_change
98
+ return if dryrun
69
99
  open(output_path, 'w'){|f| f.puts(content) }
70
100
  if (File.extname(output_path) == '.go') && !config.gofmt_disabled
71
101
  system("gofmt -w #{output_path}")
72
102
  end
73
103
  end
104
+
105
+ def process(temp_path_to_dest_path)
106
+ temp_path_to_dest_path.each{|src, dest| run(src, dest) }
107
+ end
74
108
  end
75
109
  end
@@ -1,3 +1,10 @@
1
+ <%- if user_editable? -%>
2
+ // You can edit this file. goa_model_gen doesn't overwrite this file.
3
+ <%- else -%>
4
+ // DO NOT EDIT this file.
5
+ <%- end -%>
6
+ // This code generated by goa_model_gen-<%= GoaModelGen::VERSION %>
7
+
1
8
  package <%= package %>
2
9
 
3
10
  <%- unless dependencies.empty? -%>
@@ -1,3 +1,4 @@
1
+ <%- user_editable -%>
1
2
  <%- package 'model' -%>
2
3
 
3
4
  <%-
@@ -1,14 +1,14 @@
1
1
  <%- package "model" -%>
2
2
 
3
3
  <%- source_file.types.select(&:store?).each do |model| -%>
4
- <%- store_name = "#{model.name}Store" -%>
4
+ <%- store_name = "#{model.name}Store" -%>
5
5
  <%-
6
- import(
7
- "context",
8
- "fmt",
9
- "google.golang.org/appengine/datastore",
10
- "google.golang.org/appengine/log",
11
- )
6
+ import(
7
+ "context",
8
+ "fmt",
9
+ "google.golang.org/appengine/datastore",
10
+ "google.golang.org/appengine/log",
11
+ )
12
12
  -%>
13
13
  type <%= store_name %> struct{
14
14
  <%- if model.parent -%>
@@ -32,6 +32,16 @@ func (s *<%= store_name %>) Select(ctx context.Context, q *datastore.Query) ([]*
32
32
  return r, nil
33
33
  }
34
34
 
35
+ func (s *<%= store_name %>) CountBy(ctx context.Context, q *datastore.Query) (int, error) {
36
+ g := GoonFromContext(ctx)
37
+ c, err := g.Count(q)
38
+ if err != nil {
39
+ log.Errorf(ctx, "Failed to count <%= model.name %> with %v because of %v\n", q, err)
40
+ return 0, err
41
+ }
42
+ return c, nil
43
+ }
44
+
35
45
  func (s *<%= store_name %>) Query(ctx context.Context) *datastore.Query {
36
46
  g := GoonFromContext(ctx)
37
47
  k := g.Kind(new(<%= model.name %>))
@@ -45,7 +55,7 @@ func (s *<%= store_name %>) ByID(ctx context.Context, <%= model.id_name_var %> <
45
55
  <%- else -%>
46
56
  r := <%= model.name %>{<%= model.id_name %>: <%= model.id_name_var %>}
47
57
  <%- end -%>
48
- err := s.Get(ctx, &r)
58
+ err := s.Get(ctx, &r)
49
59
  if err != nil {
50
60
  return nil, err
51
61
  }
@@ -123,54 +133,57 @@ func (s *<%= store_name %>) Exist(ctx context.Context, m *<%= model.name %>) (bo
123
133
  }
124
134
 
125
135
  func (s *<%= store_name %>) Create(ctx context.Context, m *<%= model.name %>) (*datastore.Key, error) {
126
- err := m.PrepareToCreate()
127
- if err != nil {
128
- return nil, err
129
- }
130
- if err := m.Validate(); err != nil {
131
- return nil, err
132
- }
133
-
134
- <%- if model.goon['id_type'] == 'string' -%>
135
- exist, err := s.Exist(ctx, m)
136
+ err := m.PrepareToCreate()
136
137
  if err != nil {
137
138
  return nil, err
138
139
  }
139
- if exist {
140
- log.Errorf(ctx, "Failed to create %v because of another entity has same key\n", m)
141
- return nil, fmt.Errorf("Duplicate <%= model.goon['id_name'] %> error: %q of %v\n", m.<%= model.goon['id_name'] %>, m)
142
- }
143
- <%- end -%>
144
-
145
- return s.Put(ctx, m)
140
+ return s.PutWith(ctx, m, func() error {
141
+ exist, err := s.Exist(ctx, m)
142
+ if err != nil {
143
+ return err
144
+ }
145
+ if exist {
146
+ log.Errorf(ctx, "Failed to create %v because of another entity has same key\n", m)
147
+ return fmt.Errorf("Duplicate <%= model.goon['id_name'] %> error: %q of %v\n", m.<%= model.goon['id_name'] %>, m)
148
+ }
149
+ return nil
150
+ })
146
151
  }
147
152
 
148
153
  func (s *<%= store_name %>) Update(ctx context.Context, m *<%= model.name %>) (*datastore.Key, error) {
149
- err := m.PrepareToUpdate()
150
- if err != nil {
151
- return nil, err
152
- }
153
- if err := m.Validate(); err != nil {
154
+ err := m.PrepareToUpdate()
155
+ if err != nil {
154
156
  return nil, err
155
157
  }
158
+ return s.PutWith(ctx, m, func() error {
159
+ exist, err := s.Exist(ctx, m)
160
+ if err != nil {
161
+ return err
162
+ }
163
+ if !exist {
164
+ log.Errorf(ctx, "Failed to update %v because it doesn't exist\n", m)
165
+ return fmt.Errorf("No data to update %q of %v\n", m.<%= model.goon['id_name'] %>, m)
166
+ }
167
+ return nil
168
+ })
169
+ }
156
170
 
157
- <%- if model.goon['id_type'] == 'string' -%>
158
- exist, err := s.Exist(ctx, m)
159
- if err != nil {
171
+ func (s *<%= store_name %>) PutWith(ctx context.Context, m *<%= model.name %>, f func() error) (*datastore.Key, error) {
172
+ if err := s.Validate(ctx, m); err != nil {
160
173
  return nil, err
161
174
  }
162
- if !exist {
163
- log.Errorf(ctx, "Failed to update %v because it doesn't exist\n", m)
164
- return nil, fmt.Errorf("No data to update %q of %v\n", m.<%= model.goon['id_name'] %>, m)
175
+ if f != nil {
176
+ if err := f(); err != nil {
177
+ return nil, err
178
+ }
165
179
  }
166
- <%- end -%>
167
180
 
168
- return s.Put(ctx, m)
181
+ return s.Put(ctx, m)
169
182
  }
170
183
 
171
184
  func (s *<%= store_name %>) Put(ctx context.Context, m *<%= model.name %>) (*datastore.Key, error) {
172
185
  <%- if model.goon['id_type'] == 'UUID' -%>
173
- <%- import "github.com/goadesign/goa/uuid" -%>
186
+ <%- import "github.com/goadesign/goa/uuid" -%>
174
187
  if m.Id == "" {
175
188
  m.Id = uuid.NewV4().String()
176
189
  }
@@ -193,13 +206,13 @@ func (s *<%= store_name %>) Put(ctx context.Context, m *<%= model.name %>) (*dat
193
206
  <%- if model.parent -%>
194
207
  func (s *<%= store_name %>) ValidateParent(m *<%= model.name %>) error {
195
208
  if s.ParentKey == nil {
196
- return nil
209
+ return nil
197
210
  }
198
211
  if m.ParentKey == nil {
199
212
  m.ParentKey = s.ParentKey
200
213
  }
201
214
  if !s.ParentKey.Equal(m.ParentKey) {
202
- return fmt.Errorf("Invalid ParentKey for %v", m)
215
+ return fmt.Errorf("Invalid ParentKey for %v", m)
203
216
  }
204
217
  return nil
205
218
  }
@@ -219,4 +232,26 @@ func (s *<%= store_name %>) Delete(ctx context.Context, m *<%= model.name %>) er
219
232
  return nil
220
233
  }
221
234
 
235
+ func (s *<%= store_name %>) ValidateUniqueness(ctx context.Context, m *<%= model.name %>) error {
236
+ conditions := map[string]interface{}{
237
+ <%- model.fields.select(&:unique?).each do |field| -%>
238
+ "<%= field.name %>": m.<%= field.name %>,
239
+ <%- end -%>
240
+ }
241
+ for field, value := range conditions {
242
+ q := s.Query(ctx).Filter(field + " =", value)
243
+ c, err := s.CountBy(ctx, q)
244
+ if err != nil {
245
+ return err
246
+ }
247
+ if c > 0 {
248
+ return &ValidationError{
249
+ Field: field,
250
+ Message: fmt.Sprintf("%v has already been taken", value),
251
+ }
252
+ }
253
+ }
254
+ return nil
255
+ }
256
+
222
257
  <%- end -%>
@@ -1,10 +1,26 @@
1
+ <%- user_editable -%>
1
2
  <%- package "model" -%>
2
3
 
3
4
  <%- source_file.types.select(&:store?).each do |model| -%>
4
- <%- import "gopkg.in/go-playground/validator.v9" -%>
5
- func (m *<%= model.name %>) Validate() error {
6
- validator := validator.New()
7
- return validator.Struct(m)
5
+ <%-
6
+ import "context"
7
+ import config.validator_path # "gopkg.in/go-playground/validator.v9"
8
+ -%>
9
+ <%- store_name = "#{model.name}Store" -%>
10
+ func (s *<%= store_name %>) Validate(ctx context.Context, m *<%= model.name %>) error {
11
+ if err := m.Validate(ctx); err != nil {
12
+ return err
13
+ }
14
+ if err := s.ValidateUniqueness(ctx, m); err != nil {
15
+ return err
16
+ }
17
+ return nil
18
+ }
19
+
20
+ func (m *<%= model.name %>) Validate(ctx context.Context) error {
21
+ return WithValidator(ctx, func(validate *validator.Validate) error {
22
+ return validate.Struct(m)
23
+ })
8
24
  }
9
25
 
10
26
  <%- end -%>
@@ -0,0 +1,28 @@
1
+ <%- user_editable -%>
2
+ <%- package "model" -%>
3
+ <%-
4
+ import(
5
+ "context",
6
+ config.validator_path,
7
+ # ja_translations "github.com/akm/validator/translations/ja",
8
+ )
9
+ -%>
10
+
11
+ func NewValidator(ctx context.Context) (*validator.Validate, error) {
12
+ validate := validator.New()
13
+ // trans, err := GetTranslator(ctx)
14
+ // if err != nil {
15
+ // return nil, err
16
+ // }
17
+ // // See https://github.com/go-playground/validator/blob/v9/_examples/translations/main.go
18
+ // ja_translations.RegisterDefaultTranslations(validate, trans)
19
+ return validate, nil
20
+ }
21
+
22
+ func WithValidator(ctx context.Context, f func(*validator.Validate) error) error {
23
+ validate, err := NewValidator(ctx)
24
+ if err != nil {
25
+ return err
26
+ }
27
+ return f(validate)
28
+ }
@@ -1,3 +1,3 @@
1
1
  module GoaModelGen
2
- VERSION = "0.5.0"
2
+ VERSION = "0.6.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: goa_model_gen
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - akm
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-12-05 00:00:00.000000000 Z
11
+ date: 2018-12-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -131,6 +131,7 @@ files:
131
131
  - lib/goa_model_gen/templates/model.go.erb
132
132
  - lib/goa_model_gen/templates/model_store.go.erb
133
133
  - lib/goa_model_gen/templates/model_validation.go.erb
134
+ - lib/goa_model_gen/templates/validator.go.erb
134
135
  - lib/goa_model_gen/type.rb
135
136
  - lib/goa_model_gen/version.rb
136
137
  homepage: https://github.com/akm/goa_model_gen
@@ -153,7 +154,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
153
154
  version: '0'
154
155
  requirements: []
155
156
  rubyforge_project:
156
- rubygems_version: 2.7.6
157
+ rubygems_version: 2.7.3
157
158
  signing_key:
158
159
  specification_version: 4
159
160
  summary: Generate model files for goa in golang