puppetlabs/puppet · error · ArgumentError

a data type must have an interface

Error message

a data type must have an interface

What it means

TypeBuilder#create_type requires that the definition block declared a signature via `interface '...'`. If @interface is not a String (the block never called interface, or assigned nil), ArgumentError 'a data type must have an interface' is raised. Every Puppet Ruby data type needs its attributes/functions declared as an interface string so PCore can construct the PObjectType.

Source

Thrown at lib/puppet/datatypes.rb:156

  def self.create_loaded_type(type_name, loader, &block)
    builder = TypeBuilder.new(type_name.to_s)
    api = TypeBuilderAPI.new(builder).freeze
    api.instance_eval(&block)
    builder.create_type(loader)
  end

  # @api private
  class TypeBuilder
    attr_accessor :interface, :implementation, :implementation_class

    def initialize(type_name)
      @type_name = type_name
      @implementation = nil
      @implementation_class = nil
    end

    def create_type(loader)
      raise ArgumentError, _('a data type must have an interface') unless @interface.is_a?(String)

      created_type = Puppet::Pops::Types::PObjectType.new(
        @type_name,
        Puppet::Pops::Parser::EvaluatingParser.new.parse_string("{ #{@interface} }").body
      )

      if !@implementation_class.nil?
        if @implementation_class < Puppet::Pops::Types::PuppetObject
          @implementation_class.instance_eval do
            include Puppet::Pops::Types::PuppetObject
            @_pcore_type = created_type

            def self._pcore_type
              @_pcore_type
            end
          end
        else
          Puppet::Pops::Loaders.implementation_registry.register_implementation(created_type, @implementation_class)

View on GitHub (pinned to e227c27540)

Solutions

  1. Add `interface 'attributes => { ... }'` as the first call inside the create_type block
  2. Model the interface string on Puppet's bundled data types under lib/puppet/datatypes/
  3. Declare functions in the same interface string if the type exposes behavior: `interface <<-PUPPET` heredoc works for multiline signatures

Example fix

# before
Puppet::DataTypes.create_type('Acme::Thing') do
  implementation { def version; '1.0'; end }
end
# after
Puppet::DataTypes.create_type('Acme::Thing') do
  interface 'attributes => { name => String[1] }'
  implementation { def version; '1.0'; end }
end
Defensive patterns

Strategy: validation

Validate before calling

def define_type(name, iface)
  raise ArgumentError, "#{name}: interface string required" unless iface.is_a?(String) && !iface.empty?
  Puppet::DataTypes.create_type(name) do
    interface iface
    yield if block_given?
  end
end

Type guard

def valid_interface?(s)
  s.is_a?(String) && begin
    Puppet::Pops::Parser::EvaluatingParser.new.parse_string("{ #{s} }")
    true
  rescue Puppet::ParseError
    false
  end
end

Prevention

When it happens

Trigger: Puppet::DataTypes.create_type('Acme::Thing') { implementation { ... } } with no interface call; interface assigned only on a conditional branch that does not run.

Common situations: Converting a plain Ruby class to a Puppet data type and skipping the interface; trimming boilerplate too aggressively when copying from an example type.

Related errors


AI-assisted analysis of puppetlabs/puppet@e227c27540 (2026-08-21). Data as JSON: /api/errors/18fd155b78d6fcb8. Report an issue: GitHub.