packages feed

github-actions-0.3.0.0: src/Language/Github/Actions/Job/RunsOn.hs

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingStrategies #-}

-- |
-- Module      : Language.Github.Actions.Job.RunsOn
-- Description : Runner specification for GitHub Actions jobs
-- Copyright   : (c) 2025 Bellroy Pty Ltd
-- License     : BSD-3-Clause
-- Maintainer  : Bellroy Tech Team <haskell@bellroy.com>
--
-- This module provides the 'RunsOn' type for representing the runner
-- configuration of a GitHub Actions job. GitHub Actions allows both strings
-- and lists of strings for the 'runs-on' field.
--
-- Examples of valid 'runs-on' specifications:
-- * @runs-on: ubuntu-latest@ - A single runner specified as a string
-- * @runs-on: [self-hosted, linux]@ - A runner specified as a list of labels
-- * @runs-on: [self-hosted, linux, x64]@ - A runner specified as a list of labels
--
-- For more information about GitHub Actions runner selection, see:
-- <https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#jobsjob_idruns-on>
module Language.Github.Actions.Job.RunsOn
  ( RunsOn (..),
    gen,
  )
where

import Data.Aeson (FromJSON, ToJSON (..), Value (..))
import qualified Data.Aeson as Aeson
import Data.List.NonEmpty (NonEmpty)
import Data.Text (Text)
import GHC.Generics (Generic)
import Hedgehog (MonadGen)
import qualified Hedgehog.Gen as Gen
import qualified Hedgehog.Range as Range

-- | Runner specification that preserves YAML representation.
--
-- GitHub Actions supports flexible runner specification:
--
-- * 'RunsOnString' - A single runner label as a string like @runs-on: ubuntu-latest@
-- * 'RunsOnArray' - A list of runner labels like @runs-on: [self-hosted, linux]@
--
-- Examples:
--
-- @
-- -- Single runner label (string form)
-- stringRunner :: RunsOn
-- stringRunner = RunsOnString "ubuntu-latest"
--
-- -- Multiple runner labels (array form)
-- arrayRunner :: RunsOn
-- arrayRunner = RunsOnArray ("self-hosted" :| ["linux", "x64"])
-- @
--
-- The type preserves the original YAML format during round-trip serialization.
-- A string input will serialize back to a string, and an array input will
-- serialize back to an array, preventing information loss.
data RunsOn
  = RunsOnString Text
  | RunsOnArray (NonEmpty Text)
  deriving stock (Eq, Generic, Ord, Show)

instance FromJSON RunsOn where
  parseJSON v@(Array _) = RunsOnArray <$> Aeson.parseJSON v
  parseJSON v = RunsOnString <$> Aeson.parseJSON v

instance ToJSON RunsOn where
  toJSON (RunsOnString label) = toJSON label
  toJSON (RunsOnArray labels) = toJSON labels

gen :: (MonadGen m) => m RunsOn
gen =
  Gen.choice
    [ RunsOnString <$> genText,
      RunsOnArray <$> Gen.nonEmpty (Range.linear 1 5) genText
    ]
  where
    genText = Gen.text (Range.linear 1 5) Gen.alphaNum