lib/protocol/http/header/cache_control.rb



# frozen_string_literal: true

# Released under the MIT License.
# Copyright, 2020-2023, by Samuel Williams.
# Copyright, 2023, by Thomas Morgan.

require_relative "split"

module Protocol
	module HTTP
		module Header
			# Represents the `cache-control` header, which is a list of cache directives.
			class CacheControl < Split
				# The `private` directive indicates that the response is intended for a single user and must not be stored by shared caches.
				PRIVATE = "private"
				
				# The `public` directive indicates that the response may be stored by any cache, even if it would normally be considered non-cacheable.
				PUBLIC = "public"
				
				# The `no-cache` directive indicates that caches must revalidate the response with the origin server before serving it to clients.
				NO_CACHE = "no-cache"
				
				# The `no-store` directive indicates that caches must not store the response under any circumstances.
				NO_STORE = "no-store"
				
				# The `max-age` directive indicates the maximum amount of time, in seconds, that a response is considered fresh.
				MAX_AGE = "max-age"
				
				# The `s-maxage` directive is similar to `max-age` but applies only to shared caches. If both `s-maxage` and `max-age` are present, `s-maxage` takes precedence in shared caches.
				S_MAXAGE = "s-maxage"
				
				# The `static` directive is a custom directive often used to indicate that the resource is immutable or rarely changes, allowing longer caching periods.
				STATIC = "static"
				
				# The `dynamic` directive is a custom directive used to indicate that the resource is generated dynamically and may change frequently, requiring shorter caching periods.
				DYNAMIC = "dynamic"
				
				# The `streaming` directive is a custom directive used to indicate that the resource is intended for progressive or chunked delivery, such as live video streams.
				STREAMING = "streaming"
				
				# The `must-revalidate` directive indicates that once a response becomes stale, caches must not use it to satisfy subsequent requests without revalidating it with the origin server.
				MUST_REVALIDATE = "must-revalidate"
				
				# The `proxy-revalidate` directive is similar to `must-revalidate` but applies only to shared caches.
				PROXY_REVALIDATE = "proxy-revalidate"
				
				# Initializes the cache control header with the given value. The value is expected to be a comma-separated string of cache directives.
				#
				# @parameter value [String | Nil] the raw Cache-Control header value.
				def initialize(value = nil)
					super(value&.downcase)
				end
				
				# Adds a directive to the Cache-Control header. The value will be normalized to lowercase before being added.
				#
				# @parameter value [String] the directive to add.
				def << value
					super(value.downcase)
				end
				
				# @returns [Boolean] whether the `static` directive is present.
				def static?
					self.include?(STATIC)
				end
				
				# @returns [Boolean] whether the `dynamic` directive is present.
				def dynamic?
					self.include?(DYNAMIC)
				end
				
				# @returns [Boolean] whether the `streaming` directive is present.
				def streaming?
					self.include?(STREAMING)
				end
				
				# @returns [Boolean] whether the `private` directive is present.
				def private?
					self.include?(PRIVATE)
				end
				
				# @returns [Boolean] whether the `public` directive is present.
				def public?
					self.include?(PUBLIC)
				end
				
				# @returns [Boolean] whether the `no-cache` directive is present.
				def no_cache?
					self.include?(NO_CACHE)
				end
				
				# @returns [Boolean] whether the `no-store` directive is present.
				def no_store?
					self.include?(NO_STORE)
				end
				
				# @returns [Boolean] whether the `must-revalidate` directive is present.
				def must_revalidate?
					self.include?(MUST_REVALIDATE)
				end
				
				# @returns [Boolean] whether the `proxy-revalidate` directive is present.
				def proxy_revalidate?
					self.include?(PROXY_REVALIDATE)
				end
				
				# @returns [Integer | Nil] the value of the `max-age` directive in seconds, or `nil` if the directive is not present or invalid.
				def max_age
					find_integer_value(MAX_AGE)
				end
				
				# @returns [Integer | Nil] the value of the `s-maxage` directive in seconds, or `nil` if the directive is not present or invalid.
				def s_maxage
					find_integer_value(S_MAXAGE)
				end
				
				private
				
				# Finds and parses an integer value from a directive.
				#
				# @parameter value_name [String] the directive name to search for (e.g., "max-age").
				# @returns [Integer | Nil] the parsed integer value, or `nil` if not found or invalid.
				def find_integer_value(value_name)
					if value = self.find { |value| value.start_with?(value_name) }
						_, age = value.split("=", 2)
						
						if age =~ /\A[0-9]+\z/
							return Integer(age)
						end
					end
				end
			end
		end
	end
end