Class: DomainSanity::Subject

Inherits:
Object
  • Object
show all
Defined in:
lib/domain_sanity/subject.rb,
lib/domain_sanity/subject.rb

Overview

A classified view of one subject string. Subject.for inspects the input once and returns the concrete type that fits it - Hostname, Wildcard, IPSubject, ReverseZone, or MalformedSubject - so a method that only makes sense for one kind (registrable_domain for a host, public? for an IP) lives only on that type instead of returning nil on a god-object.

Facts are computed lazily and memoized, so asking one question is cheap and asking many re-parses nothing. valid? means the same thing on every subject and matches DomainSanity.valid? exactly: true only for a structurally valid plain host name. Wildcards, IPs, and reverse zones are not "valid" in that sense and expose their own predicates instead.

Constant Summary collapse

TYPES =
{
  hostname: Hostname,
  wildcard: Wildcard,
  ip: IPSubject,
  reverse_zone: ReverseZone
}.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(input, normalized, policy) ⇒ Subject

Returns a new instance of Subject.



32
33
34
35
36
# File 'lib/domain_sanity/subject.rb', line 32

def initialize(input, normalized, policy)
  @input = input
  @norm = normalized
  @policy = policy
end

Instance Attribute Details

#inputObject (readonly)

Returns the value of attribute input.



21
22
23
# File 'lib/domain_sanity/subject.rb', line 21

def input
  @input
end

#policyObject (readonly)

Returns the value of attribute policy.



21
22
23
# File 'lib/domain_sanity/subject.rb', line 21

def policy
  @policy
end

Class Method Details

.for(input, policy: :ca_baseline) ⇒ Object

Classify input under policy (a preset symbol, Hash, or Policy) and return the matching Subject subclass.



25
26
27
28
29
30
# File 'lib/domain_sanity/subject.rb', line 25

def self.for(input, policy: :ca_baseline)
  pol = Policy.coerce(policy)
  norm = Name.normalize(input, pol)
  klass = TYPES.fetch(norm.kind, MalformedSubject)
  klass.new(input, norm, pol)
end

Instance Method Details

#asciiObject



78
79
80
81
82
# File 'lib/domain_sanity/subject.rb', line 78

def ascii
  return @ascii if defined?(@ascii)

  @ascii = IDN.to_ascii(@input)
end

#hostname?Boolean

Returns:

  • (Boolean)


48
49
50
# File 'lib/domain_sanity/subject.rb', line 48

def hostname?
  false
end

#ip?Boolean

Returns:

  • (Boolean)


60
61
62
# File 'lib/domain_sanity/subject.rb', line 60

def ip?
  false
end

#kindObject



38
39
40
# File 'lib/domain_sanity/subject.rb', line 38

def kind
  @norm.kind
end

#punycode?Boolean

Returns:

  • (Boolean)


72
73
74
75
76
# File 'lib/domain_sanity/subject.rb', line 72

def punycode?
  return @punycode if defined?(@punycode)

  @punycode = IDN.punycode?(@input)
end

#reasonsObject



68
69
70
# File 'lib/domain_sanity/subject.rb', line 68

def reasons
  @reasons ||= Name.reasons_for(@norm, policy: @policy).freeze
end

#reverse_zone?Boolean

Returns:

  • (Boolean)


64
65
66
# File 'lib/domain_sanity/subject.rb', line 64

def reverse_zone?
  false
end

#to_hObject

A uniform snapshot: every subject exposes the same keys, with nil where a fact does not apply to this kind. (The typed methods stay kind-specific - an IPSubject still has no #registrable_domain - but the serialized shape is stable so downstream present?/dig checks work the same for any kind.) Subclasses fill the optional slots via #type_facts.



95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/domain_sanity/subject.rb', line 95

def to_h
  {
    input: @input,
    kind: kind,
    valid: valid?,
    wildcard: wildcard?,
    valid_wildcard: valid_wildcard?,
    ip: ip?,
    reserved_ip: nil,
    public_ip: nil,
    reverse_zone: reverse_zone?,
    punycode: punycode?,
    ascii: ascii,
    unicode: unicode,
    registrable_domain: nil,
    public_suffix: nil,
    reasons: reasons.map(&:to_s)
  }.merge(type_facts)
end

#type_factsObject

Kind-specific overrides for the optional to_h slots. Base has none.



116
117
118
# File 'lib/domain_sanity/subject.rb', line 116

def type_facts
  {}
end

#unicodeObject



84
85
86
87
88
# File 'lib/domain_sanity/subject.rb', line 84

def unicode
  return @unicode if defined?(@unicode)

  @unicode = IDN.to_unicode(@input)
end

#valid?Boolean

Only a structurally valid plain host name is "valid"; every other kind overrides nothing and stays false. See Hostname.

Returns:

  • (Boolean)


44
45
46
# File 'lib/domain_sanity/subject.rb', line 44

def valid?
  false
end

#valid_wildcard?Boolean

Returns:

  • (Boolean)


56
57
58
# File 'lib/domain_sanity/subject.rb', line 56

def valid_wildcard?
  false
end

#wildcard?Boolean

Returns:

  • (Boolean)


52
53
54
# File 'lib/domain_sanity/subject.rb', line 52

def wildcard?
  false
end