summaryrefslogtreecommitdiff
path: root/lib/gitlab/graphql/deprecation.rb
blob: d30751fe46ecd67e97f2c2318b1defc25de32a4f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# frozen_string_literal: true

module Gitlab
  module Graphql
    class Deprecation
      REASON_RENAMED = :renamed
      REASON_ALPHA = :alpha

      REASONS = {
        REASON_RENAMED => 'This was renamed.',
        REASON_ALPHA => 'This feature is in Alpha. It can be changed or removed at any time.'
      }.freeze

      include ActiveModel::Validations

      validates :milestone, presence: true, format: { with: /\A\d+\.\d+\z/, message: 'must be milestone-ish' }
      validates :reason, presence: true
      validates :reason,
                format: { with: /.*[^.]\z/, message: 'must not end with a period' },
                if: :reason_is_string?
      validate :milestone_is_string
      validate :reason_known_or_string

      def self.parse(options)
        new(**options) if options
      end

      def initialize(reason: nil, milestone: nil, replacement: nil)
        @reason = reason.presence
        @milestone = milestone.presence
        @replacement = replacement.presence
      end

      def ==(other)
        return false unless other.is_a?(self.class)

        [reason_text, milestone, replacement] == [:reason_text, :milestone, :replacement].map do |attr|
          other.send(attr) # rubocop: disable GitlabSecurity/PublicSend
        end
      end
      alias_method :eql, :==

      def markdown(context: :inline)
        parts = [
          "#{changed_in_milestone(format: :markdown)}.",
          reason_text,
          replacement_markdown.then { |r| "Use: #{r}." if r }
        ].compact

        case context
        when :block
          ['WARNING:', *parts].join("\n")
        when :inline
          parts.join(' ')
        end
      end

      def replacement_markdown
        return unless replacement.present?
        return "`#{replacement}`" unless replacement.include?('.') # only fully qualified references can be linked

        "[`#{replacement}`](##{replacement.downcase.tr('.', '')})"
      end

      def edit_description(original_description)
        @original_description = original_description
        return unless original_description

        original_description + description_suffix
      end

      def original_description
        return unless @original_description
        return @original_description if @original_description.ends_with?('.')

        "#{@original_description}."
      end

      def deprecation_reason
        [
          reason_text,
          replacement && "Please use `#{replacement}`.",
          "#{changed_in_milestone}."
        ].compact.join(' ')
      end

      private

      attr_reader :reason, :milestone, :replacement

      def milestone_is_string
        return if milestone.is_a?(String)

        errors.add(:milestone, 'must be a string')
      end

      def reason_known_or_string
        return if REASONS.key?(reason)
        return if reason_is_string?

        errors.add(:reason, 'must be a known reason or a string')
      end

      def reason_is_string?
        reason.is_a?(String)
      end

      def reason_text
        @reason_text ||= REASONS[reason] || "#{reason.to_s.strip}."
      end

      def description_suffix
        " #{changed_in_milestone}: #{reason_text}"
      end

      # Returns 'Deprecated in <milestone>' for proper deprecations.
      # Retruns 'Introduced in <milestone>' for :alpha deprecations.
      # Formatted to markdown or plain format.
      def changed_in_milestone(format: :plain)
        verb = if reason == REASON_ALPHA
                 'Introduced'
               else
                 'Deprecated'
               end

        case format
        when :plain
          "#{verb} in #{milestone}"
        when :markdown
          "**#{verb}** in #{milestone}"
        end
      end
    end
  end
end