Skip to content

Commit

Permalink
add nested decoder
Browse files Browse the repository at this point in the history
  • Loading branch information
emilymye committed Mar 15, 2019
1 parent 76d08f0 commit 18945d5
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 9 deletions.
22 changes: 22 additions & 0 deletions api/resource.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ module Properties
# list. Otherwise, it's safe to leave empty.
# If empty, we assume that `name` is the identifier.
attr_reader :identity
# This is useful in case you need to change the query made for
# GET requests only. In particular, this is often used
# to extract an object from a parent object or a collection.
attr_reader :nested_decoder

attr_reader :exclude
attr_reader :async
attr_reader :readonly
Expand Down Expand Up @@ -100,6 +105,18 @@ def validate
end
end

class NestedDecoder < Api::Object
attr_reader :keys
attr_reader :has_id_only_list

def validate
super

check :keys, type: Array, item_type: String, required: true
check :has_id_only_list, type: :boolean, default: false
end
end

# Represents a response from the API that returns a list of objects.
class ResponseList < Api::Object
attr_reader :kind
Expand Down Expand Up @@ -190,6 +207,11 @@ def validate
check :transport, type: Transport
check :references, type: ReferenceLinks

check :nested_decoder, type: Api::Resource::NestedDecoder
if @nested_decoder&.has_id_only_list && @identity&.length != 1
raise 'Resource with :has_id_only_list in :nested_decoder must have exactly one :identity property"'
end

check :collection_url_response, default: Api::Resource::ResponseList.new,
type: Api::Resource::ResponseList

Expand Down
55 changes: 55 additions & 0 deletions templates/terraform/nested_decoder.go.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
config := meta.(*Config)

var v interface{}
var ok bool
<% object.nested_decoder.keys[0...-1].each do |k| -%>
v, ok = res["<%=k-%>"]
if !ok || v == nil {
return nil, nil
}
res = v.(map[string]interface{})
<% end -%>
v, ok = res["<%=object.nested_decoder.keys[-1]-%>"]
if !ok || v == nil {
return nil, nil
}
// Final nested resource is either a list of resources we need to filter
// or just the resource itself, which we return.
switch v.(type) {
case []interface{}:
break
case map[string]interface{}:
return v.(map[string]interface{}), nil
default:
return nil, fmt.Errorf("invalid value for <%= object.nested_decoder.keys.join(".") -%>: %v", v)
}

items := v.([]interface{})
for _, vRaw := range items {
<% if object.nested_decoder.has_id_only_list -%>
// If only an id is given in parent resource,
// construct a resource map for that id KV pair.
item := map[string]interface{}{"<%=object.identity.first.api_name%>": vRaw}
<% else %>
item := vRaw.(map[string]interface{})
<% end %>

<% object.identity.each do |prop| -%>
<% if settable_properties.include?(prop) -%>
id, err := expand<%= resource_name -%><%= titlelize_property(prop) -%>(d.Get("<%= prop.name.underscore -%>"), d, config)
if err != nil {
return nil, err
}
<% else -%>
id := d.Get("<%= prop.name.underscore -%>")
<% end # settable_properties.include?(prop)-%>

itemId := flatten<%= resource_name -%><%= titlelize_property(prop) -%>(item["<%= prop.api_name %>"], d)
log.Printf("[DEBUG] Checking equality of %#v, %#v", itemId, id)
if !reflect.DeepEqual(itemId, id) {
continue
}
<% end # object.identity.each -%>
return item, nil
}
return nil, nil
31 changes: 22 additions & 9 deletions templates/terraform/resource.erb
Original file line number Diff line number Diff line change
Expand Up @@ -217,15 +217,19 @@ func resource<%= resource_name -%>Read(d *schema.ResourceData, meta interface{})
return handleNotFoundError(err, d, fmt.Sprintf("<%= resource_name -%> %q", d.Id()))
}

<% unless object.self_link_query.nil? -%>
<%# This part of the template extracts the one resource we're interested in
from the list that gets returned. self_link_query is a field which
describes a list result from a read. -%>
<%= compile_template('templates/terraform/self_link_query.erb',
object: object,
settable_properties: object.settable_properties,
resource_name: resource_name) -%>
<% end -%>
<% if object.nested_decoder -%>
res, err = resource<%= resource_name -%>Extractor(d, meta, res)
if err != nil {
return err
}

if res == nil {
// Object isn't there any more - remove it from the state.
log.Printf("[DEBUG] Removing <%= resource_name -%> because it couldn't be matched.")
d.SetId("")
return nil
}
<% end -%>

<% if object.custom_code.decoder -%>
res, err = resource<%= resource_name -%>Decoder(d, meta, res)
Expand Down Expand Up @@ -606,6 +610,15 @@ func resource<%= resource_name -%>UpdateEncoder(d *schema.ResourceData, meta int
}
<% end -%>

<% if object.nested_decoder -%>
func resource<%= resource_name -%>Extractor(d *schema.ResourceData, meta interface{}, res map[string]interface{}) (map[string]interface{}, error) {
<%= compile_template('templates/terraform/nested_decoder.go.erb',
object: object,
settable_properties: object.settable_properties,
resource_name: resource_name) -%>
}
<% end -%>

<% if object.custom_code.decoder -%>
func resource<%= resource_name -%>Decoder(d *schema.ResourceData, meta interface{}, res map[string]interface{}) (map[string]interface{}, error) {
<%= lines(compile(object.custom_code.decoder)) -%>
Expand Down

0 comments on commit 18945d5

Please sign in to comment.