Initial commit.

This commit is contained in:
2026-08-05 12:06:46 +05:00
commit f37a1422e2
21 changed files with 1026 additions and 0 deletions
+61
View File
@@ -0,0 +1,61 @@
package configuration
import (
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
"strings"
)
var errCfg = errors.New("configuration")
// Config is a configuration controlling structure.
type Config struct {
RSS *RSS `json:"rss"`
Outputs *Outputs `json:"outputs"`
}
// New creates new configuration controller.
func New(path string) (*Config, error) {
cfg := &Config{}
// Get configuration file real path.
userHomeDir, err := os.UserHomeDir()
if err != nil {
return nil, fmt.Errorf("%w: parse configuration file: get real path: get user home dir: %w", errCfg, err)
}
path = strings.Replace(path, "~", userHomeDir, 1)
absPath, err := filepath.Abs(path)
if err != nil {
return nil, fmt.Errorf("%w: parse configuration file: get real path: get absolute path: %w", errCfg, err)
}
cfgFileData, err := os.ReadFile(absPath)
if err != nil {
return nil, fmt.Errorf("%w: parse configuration file: read file: %w", errCfg, err)
}
if err := json.Unmarshal(cfgFileData, cfg); err != nil {
return nil, fmt.Errorf("%w: parse configuration file: %w", errCfg, err)
}
// Validate configuration.
if err := cfg.Validate(); err != nil {
return nil, fmt.Errorf("%w: %w", errCfg, err)
}
return cfg, nil
}
// Validate validates app configuration.
func (c *Config) Validate() error {
if err := c.RSS.Validate(); err != nil {
return fmt.Errorf("validate configuration: %w", err)
}
return nil
}
+48
View File
@@ -0,0 +1,48 @@
package configuration
import (
"errors"
"fmt"
"net/url"
)
var (
errDestIsEmpty = errors.New("dest is empty")
errFeedValidate = errors.New("feed configuration validation")
errNameIsEmpty = errors.New("name is empty")
errOutputIsEmpty = errors.New("output is empty")
errURLIsEmpty = errors.New("URL is empty")
)
// Feed is a single RSS feed configuration.
type Feed struct {
Name string `json:"name"`
URL string `json:"url"`
Output string `json:"output"`
Destination string `json:"dest"`
}
// Validate validates RSS feed configuration.
func (f *Feed) Validate() error {
if f.Name == "" {
return fmt.Errorf("%w: %w", errFeedValidate, errNameIsEmpty)
}
if f.Output == "" {
return fmt.Errorf("%w: %w", errFeedValidate, errOutputIsEmpty)
}
if f.Destination == "" {
return fmt.Errorf("%w: %w", errFeedValidate, errDestIsEmpty)
}
if f.URL == "" {
return fmt.Errorf("%w: %w", errFeedValidate, errURLIsEmpty)
}
if _, err := url.Parse(f.URL); err != nil {
return fmt.Errorf("%w: parse feed URL: %w", errFeedValidate, err)
}
return nil
}
+22
View File
@@ -0,0 +1,22 @@
package configuration
import (
"errors"
"fmt"
)
var errRequestTimeoutTooLow = errors.New("request timeout is too low")
// HTTPClient is an HTTP client configuration.
type HTTPClient struct {
RequestTimeoutSeconds int64 `json:"request_timeout_seconds"`
}
// Validate validates configuration for HTTP clients.
func (h *HTTPClient) Validate() error {
if h.RequestTimeoutSeconds < 1 {
return fmt.Errorf("validate HTTP client configuration: %w", errRequestTimeoutTooLow)
}
return nil
}
+44
View File
@@ -0,0 +1,44 @@
package configuration
import (
"errors"
"fmt"
"net"
)
var (
errNNTPAddressIsEmpty = errors.New("address is empty")
errNNTPInvalidAddress = errors.New("address is invalid")
errNNTPInvalidConnTimeout = errors.New("connect timeout is invalid")
errNNTPUserOrPassIsEmpty = errors.New("username or password is empty")
)
// NNTP is an NNTP output configuration.
type NNTP struct {
Address string `json:"address"`
Username string `json:"username"`
Password string `json:"password"`
ConnectTimeout int64 `json:"connect_timeout"`
}
// Validate validates configuration.
func (n *NNTP) Validate() error {
if n.Address == "" {
return fmt.Errorf("validate NNTP output configuration: %w", errNNTPAddressIsEmpty)
}
_, _, err := net.SplitHostPort(n.Address)
if err != nil {
return fmt.Errorf("validate NNTP output configuration: %w: %w", errNNTPInvalidAddress, err)
}
if (n.Username != "" && n.Password == "") || (n.Username == "" && n.Password != "") {
return fmt.Errorf("validate NNTP output configuration: %w", errNNTPUserOrPassIsEmpty)
}
if n.ConnectTimeout < 1 {
return fmt.Errorf("validate NNTP output configuration: %w", errNNTPInvalidConnTimeout)
}
return nil
}
+17
View File
@@ -0,0 +1,17 @@
package configuration
import "fmt"
// Outputs is an outputs configuration.
type Outputs struct {
NNTP *NNTP `json:"nntp"`
}
// Validate validates configuration.
func (o *Outputs) Validate() error {
if err := o.NNTP.Validate(); err != nil {
return fmt.Errorf("validate outputs configuration: %w", err)
}
return nil
}
+29
View File
@@ -0,0 +1,29 @@
package configuration
import (
"errors"
"fmt"
)
var errRSSValidate = errors.New("validate RSS configuration")
// RSS is an RSS parser configuration.
type RSS struct {
HTTPClient *HTTPClient `json:"http_client"`
Feeds []*Feed `json:"feeds"`
}
// Validate validates RSS parser configuration.
func (r *RSS) Validate() error {
for idx, feed := range r.Feeds {
if err := feed.Validate(); err != nil {
return fmt.Errorf("%w: feed %d: %w", errRSSValidate, idx, err)
}
}
if err := r.HTTPClient.Validate(); err != nil {
return fmt.Errorf("%w: %w", errRSSValidate, err)
}
return nil
}