Reinhardt Settings System Documentation

Overview

Reinhardt provides a flexible and powerful configuration management system. By combining TOML-format configuration files with environment variables, it enables consistent configuration management across all environments, from development to production.

Key Features

  1. TOML Format: Easy-to-read and write configuration files
  2. Environment-Specific Settings: Different configurations for local, staging, production, etc.
  3. Priority System: Integrates settings from multiple sources with clear priority rules
  4. Composable Fragments: Build your settings struct from reusable fragments with the #[settings] macro
  5. No Recompilation Required: No need to rebuild Rust code when changing settings
  6. Secure: Protects sensitive information with .gitignore
  7. Extensible: Easy to add custom sources and configuration items

Quick Start

1. Project Setup

Projects created with the reinhardt-admin startproject command already include a settings/ directory:

my-project/
├── settings/
│   ├── .gitignore         # Ignores *.toml, only commits *.example.toml
│   ├── base.example.toml  # Base configuration template
│   ├── local.example.toml # Local development template
│   ├── staging.example.toml
│   └── production.example.toml
└── src/
    └── config/
        └── settings.rs    # Settings loading logic

2. Creating Configuration Files

# Copy example files to create actual configuration files
cd my-project/settings
cp base.example.toml base.toml
cp local.example.toml local.toml
cp staging.example.toml staging.toml
cp production.example.toml production.toml

3. Editing Configuration Files

settings/base.toml:

debug = false
secret_key = "your-secret-key-here"

[databases.default]
engine = "postgresql"
host = "localhost"
port = 5432
name = "mydb"
user = "postgres"
password = "change-this"

settings/local.toml:

debug = true
secret_key = "development-secret-key"

[databases.default]
name = "mydb_dev"
password = "local-password"

4. Settings Loading Code

src/config/settings.rs:

use reinhardt::settings;
use reinhardt::{
	CoreSettings, DefaultSource, LowPriorityEnvSource, Profile, SettingsBuilder, TomlFileSource,
};
use std::env;

#[settings(core: CoreSettings /* add fragments here, e.g.: | cache: CacheSettings | session: SessionSettings */)]
pub struct ProjectSettings;

pub fn get_settings() -> ProjectSettings {
	let profile_str = env::var("REINHARDT_ENV").unwrap_or_else(|_| "dev".to_string());
	let profile = Profile::parse(&profile_str);
	let settings_dir = env::current_dir().expect("Failed to get current directory").join("settings");

	SettingsBuilder::new()
		.profile(profile)
		.add_source(DefaultSource::new())
		.add_source(LowPriorityEnvSource::new().with_prefix("REINHARDT_"))
		.add_source(TomlFileSource::new(settings_dir.join("base.toml")))
		.add_source(TomlFileSource::new(settings_dir.join(format!("{}.toml", profile_str))))
		.build()
		.expect("Failed to build settings")
		.into_typed()
		.expect("Failed to convert settings")
}

5. Starting the Application

# Start in development environment (default)
cargo make runserver

# Explicitly specify environment
REINHARDT_ENV=dev cargo make runserver
REINHARDT_ENV=staging cargo make runserver
REINHARDT_ENV=production cargo make runserver

Settings Priority

Settings are merged based on the priority value of each source. Sources with higher priority values override sources with lower priority values.

Priority Values by Source Type

The table below lists sources in order of priority (highest to lowest):

Source TypePriority ValueDescription
EnvSource100Highest priority - Environment variables (override all other sources)
DotEnvSource90.env file variables (override TOML and defaults)
TomlFileSource50TOML configuration files (override defaults and low-priority env vars)
LowPriorityEnvSource40Low-priority environment variables (overridden by TOML files)
DefaultSource0Lowest priority - Default values defined in code

Common Configuration Pattern

The documentation examples use this configuration pattern:

SettingsBuilder::new()
    .add_source(LowPriorityEnvSource::new().with_prefix("REINHARDT_"))
    .add_source(TomlFileSource::new("settings/base.toml"))
    .add_source(TomlFileSource::new("settings/local.toml"))
    .build()

Priority order for this pattern (highest priority first):

  1. Environment-Specific TOML Files (local.toml, staging.toml, production.toml) - Priority 50
  2. Base TOML File (base.toml) - Priority 50
  3. Low-Priority Environment Variables (REINHARDT_ prefix with LowPriorityEnvSource) - Priority 40
  4. Default Values (defined in code) - Priority 0

Note: When multiple sources have the same priority (e.g., base.toml and local.toml), sources added later override earlier ones.

Environment Variable Priority Options

You can choose between two environment variable sources depending on your needs:

.add_source(LowPriorityEnvSource::new().with_prefix("REINHARDT_"))
  • Priority: 40 (lower than TOML files)
  • TOML files override environment variables
  • Useful for setting defaults that can be overridden by configuration files
.add_source(EnvSource::new().with_prefix("REINHARDT_"))
  • Priority: 100 (higher than TOML files)
  • Environment variables override TOML files
  • Useful for production deployments where environment variables should take precedence

Example with LowPriorityEnvSource

# base.toml
debug = false
secret_key = "base-secret"

[databases.default]
host = "localhost"
port = 5432
# local.toml
debug = true

[databases.default]
host = "127.0.0.1"
# Environment variable (using LowPriorityEnvSource)
export REINHARDT_DATABASE_PORT=5433

Result with LowPriorityEnvSource:

  • debug = true (local.toml overrides base.toml)
  • secret_key = "base-secret" (not defined in local.toml, uses base.toml value)
  • databases.default.host = "127.0.0.1" (local.toml overrides base.toml)
  • databases.default.port = 5432 (base.toml overrides environment variable because TOML has higher priority than LowPriorityEnvSource)

Result if using EnvSource instead:

  • debug = true (local.toml value)
  • secret_key = "base-secret" (base.toml value)
  • databases.default.host = "127.0.0.1" (local.toml value)
  • databases.default.port = 5433 (environment variable overrides TOML because EnvSource has higher priority)

Configuration File Structure

Base Configuration (base.toml)

Common settings for all environments:

debug = false
secret_key = "CHANGE_THIS_IN_PRODUCTION"
allowed_hosts = ["localhost", "127.0.0.1"]

[databases.default]
engine = "postgresql"
host = "localhost"
port = 5432
name = "mydb"
user = "postgres"
password = "CHANGE_THIS"

[static_files]
url = "/static/"
root = "static/"

[media]
url = "/media/"
root = "media/"

[logging]
level = "info"
format = "json"

Local Development Environment (local.toml)

Settings for development:

debug = true
secret_key = "dev-secret-key-not-for-production"

[databases.default]
name = "mydb_dev"
password = "local-dev-password"

[logging]
level = "debug"
format = "pretty"

Staging Environment (staging.toml)

debug = false
secret_key = "staging-secret-key"
allowed_hosts = ["staging.example.com"]

[databases.default]
host = "staging-db.example.com"
name = "mydb_staging"
password = "staging-db-password"

[logging]
level = "info"
format = "json"

Production Environment (production.toml)

debug = false
secret_key = "production-secret-key-from-secret-manager"
allowed_hosts = ["www.example.com", "api.example.com"]

[databases.default]
host = "prod-db.example.com"
port = 5432
name = "mydb_production"
user = "app_user"
password = "production-db-password"

[static_files]
url = "https://cdn.example.com/static/"

[media]
url = "https://cdn.example.com/media/"

[logging]
level = "warn"
format = "json"

Composable Settings Architecture

Reinhardt's settings system is built on a composable fragment architecture. Instead of a monolithic Settings struct, you compose your ProjectSettings from reusable fragments using the #[settings] macro.

The SettingsFragment Trait

Every settings fragment implements the SettingsFragment trait, which defines the TOML section name and optional profile-based validation:

pub trait SettingsFragment:
	Clone + Debug + Serialize + DeserializeOwned + Send + Sync + 'static
{
	/// The accessor trait for this fragment.
	/// Use `()` for fragments without a dedicated accessor trait.
	type Accessor: ?Sized;

	/// The TOML section key for this fragment (e.g., "core", "cache").
	fn section() -> &'static str;

	/// Returns the default field policies for this fragment.
	fn field_policies() -> &'static [FieldPolicy] {
		&[]
	}

	/// Optional validation that runs after deserialization.
	/// Override this to add profile-specific validation logic.
	fn validate(&self, _profile: &Profile) -> ValidationResult {
		Ok(())
	}
}

Available Fragments

Reinhardt provides 12 built-in fragments:

FragmentField NameTOML SectionHas Trait
CoreSettingscore(flattened)HasCoreSettings
SecuritySettingssecurity[security]HasSecuritySettings
CacheSettingscache[cache]HasCacheSettings
SessionSettingssession[session]HasSessionSettings
CorsSettingscors[cors]HasCorsSettings
StaticSettingsstatic_files[static_files]HasStaticSettings
MediaSettingsmedia[media]HasMediaSettings
EmailSettingsemail[email]HasEmailSettings
LoggingSettingslogging[logging]HasLoggingSettings
I18nSettingsi18n[i18n]HasI18nSettings
TemplateSettingstemplates[templates]HasTemplateSettings
ContactSettingscontacts[contacts]HasContactSettings

Note: CoreSettings uses #[serde(flatten)], so its fields (debug, secret_key, database, etc.) appear at the top level in TOML files rather than under a [core] section.

The #[settings] Macro

The #[settings] macro generates a ProjectSettings struct with the specified fragments. All fragments, including CoreSettings, must be declared explicitly.

Basic Usage (CoreSettings only)

use reinhardt::settings;
use reinhardt::CoreSettings;

#[settings(core: CoreSettings)]
pub struct ProjectSettings;

// Generated struct has a `core` field of type CoreSettings.
// Access: settings.core.debug, settings.core.secret_key, etc.

Adding Fragments

Use field_name: FragmentType syntax, separated by |:

use reinhardt::settings;
use reinhardt::CoreSettings;
use reinhardt::conf::{CacheSettings, SessionSettings, CorsSettings};

#[settings(core: CoreSettings | cache: CacheSettings | session: SessionSettings | cors: CorsSettings)]
pub struct ProjectSettings;

// Access:
// settings.core.debug
// settings.cache.backend
// settings.session.cookie_name
// settings.cors.allow_origins

Without CoreSettings

If you don't need CoreSettings, simply omit it:

use reinhardt::settings;
use reinhardt::conf::CacheSettings;

#[settings(cache: CacheSettings)]
pub struct ProjectSettings;

// Only has settings.cache, no settings.core

Has* Accessor Traits

Each fragment has a corresponding Has* trait that enables generic programming over settings types. This is useful for writing functions that only require specific fragments.

Note: You do not need to implement Has*Settings traits manually. The #[settings] macro automatically provides blanket implementations for all declared fragments. To use method-call syntax like settings.core() in generic bounds, import the specific Has*Settings trait:

use reinhardt::conf::{HasCoreSettings, HasCacheSettings};

fn configure_app<S: HasCoreSettings + HasCacheSettings>(settings: &S) {
	let debug = settings.core().debug;
	let cache_backend = &settings.cache().backend;
	// Use settings without knowing the concrete ProjectSettings type
}

When using the #[settings] macro, you only need to import the fragment types (e.g., CoreSettings), not the accessor traits — the macro generates all necessary trait implementations automatically.

Profile-Based Validation

Fragments can implement custom validation logic that runs based on the active profile:

use reinhardt::SettingsFragment;
use reinhardt::Profile;
use serde::{Deserialize, Serialize};

#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct ApiSettings {
	pub timeout: u64,
	pub max_retries: u32,
	pub base_url: String,
}

impl SettingsFragment for ApiSettings {
	type Accessor = ();

	fn section() -> &'static str { "api" }

	fn validate(&self, profile: &Profile) -> ValidationResult {
		if profile.is_production() && self.base_url.starts_with("http://") {
			return Err("Production API base_url must use HTTPS".into());
		}
		Ok(())
	}
}

Configuration via Environment Variables

You can configure settings using environment variables. The priority of environment variables depends on which source you use:

  • LowPriorityEnvSource (Priority: 40): TOML files override environment variables
  • EnvSource (Priority: 100): Environment variables override TOML files

The examples in this document use LowPriorityEnvSource, which means TOML files have higher priority than environment variables.

Naming Convention

Environment variable names follow this convention:

REINHARDT_<KEY_NAME>
REINHARDT_<SECTION_NAME>_<KEY_NAME>

Examples

# Top-level settings
export REINHARDT_DEBUG=true
export REINHARDT_SECRET_KEY="env-secret-key"

# Settings within sections
export REINHARDT_DATABASE_HOST=localhost
export REINHARDT_DATABASE_PORT=5432
export REINHARDT_DATABASE_NAME=mydb
export REINHARDT_DATABASE_USER=postgres
export REINHARDT_DATABASE_PASSWORD=mypassword

# Nested settings
export REINHARDT_LOGGING_LEVEL=debug
export REINHARDT_STATIC_URL=/static/

Choosing Between EnvSource and LowPriorityEnvSource

Use LowPriorityEnvSource when:

  • You want TOML files to override environment variables
  • You're in development and want configuration files to take precedence
  • You want environment variables as fallback defaults
SettingsBuilder::new()
    .add_source(LowPriorityEnvSource::new().with_prefix("REINHARDT_"))
    .add_source(TomlFileSource::new("settings/base.toml"))
    .add_source(TomlFileSource::new("settings/local.toml"))
    .build()
// Result: local.toml > base.toml > environment variables > defaults

Use EnvSource when:

  • You want environment variables to override TOML files
  • You're in production and want environment variables to take precedence
  • You're deploying to cloud platforms (Heroku, AWS, etc.) that use environment variables
SettingsBuilder::new()
    .add_source(DefaultSource::new())
    .add_source(TomlFileSource::new("settings/base.toml"))
    .add_source(EnvSource::new().with_prefix("REINHARDT_"))
    .build()
// Result: environment variables > base.toml > defaults

Managing Configuration with Environment Variables Only

If you want to manage configuration using only environment variables without TOML files, use EnvSource:

use reinhardt::settings;
use reinhardt::{CoreSettings, DefaultSource, EnvSource, SettingsBuilder};

#[settings(core: CoreSettings)]
pub struct ProjectSettings;

pub fn get_settings() -> ProjectSettings {
	SettingsBuilder::new()
		.add_source(DefaultSource::new())
		.add_source(EnvSource::new().with_prefix("REINHARDT_"))
		.build()
		.expect("Failed to build settings")
		.into_typed()
		.expect("Failed to convert settings")
}

Loading and Accessing Settings

Basic Loading

src/config/settings.rs:

use reinhardt::settings;
use reinhardt::{
	CoreSettings, DefaultSource, LowPriorityEnvSource, Profile, SettingsBuilder, TomlFileSource,
};
use std::env;

#[settings(core: CoreSettings)]
pub struct ProjectSettings;

pub fn get_settings() -> ProjectSettings {
	let profile_str = env::var("REINHARDT_ENV").unwrap_or_else(|_| "dev".to_string());
	let profile = Profile::parse(&profile_str);
	let settings_dir = env::current_dir().expect("Failed to get current directory").join("settings");

	SettingsBuilder::new()
		.profile(profile)
		.add_source(DefaultSource::new())
		.add_source(LowPriorityEnvSource::new().with_prefix("REINHARDT_"))
		.add_source(TomlFileSource::new(settings_dir.join("base.toml")))
		.add_source(TomlFileSource::new(settings_dir.join(format!("{}.toml", profile_str))))
		.build()
		.expect("Failed to build settings")
		.into_typed()
		.expect("Failed to convert settings")
}

Using in Application

Access settings anywhere in your application code:

use crate::config::settings::get_settings;

let settings = get_settings();

println!("Debug mode: {}", settings.core.debug);
println!("Secret key: {}", settings.core.secret_key);

Security Best Practices

1. .gitignore Configuration

settings/.gitignore:

# Actual configuration files are not tracked by Git
*.toml

# Only commit example files
!*.example.toml

Project root .gitignore:

# Configuration files
settings/*.toml
!settings/*.example.toml

# Environment variable files
.env
.env.local
.env.*.local

2. Creating Example Files

# Create example files from actual configuration files
cd settings
cp base.toml base.example.toml
cp local.toml local.example.toml
cp staging.toml staging.example.toml
cp production.toml production.example.toml

Important: Remove sensitive information from *.example.toml files:

# Don't include production secrets
secret_key = "actual-production-secret-key"
password = "real-database-password"

# Use placeholders
secret_key = "CHANGE_THIS_IN_PRODUCTION"
password = "CHANGE_THIS"

3. Managing Sensitive Information

For production environments, it's recommended not to write sensitive information directly in TOML files:

Option 1: Environment Variables (lower priority)

export REINHARDT_SECRET_KEY="actual-secret-from-vault"
export REINHARDT_DATABASE_PASSWORD="actual-db-password"

Option 2: Secret Management Systems

For production deployments, integrate with secret management systems such as AWS Secrets Manager, HashiCorp Vault, or Azure Key Vault by implementing a custom ConfigSource that fetches secrets at startup. See Implementing Custom Sources for details.


Adding Custom Settings

Creating a Custom Fragment

Define a struct that implements SettingsFragment to add project-specific configuration:

src/config/api_settings.rs:

use reinhardt::SettingsFragment;
use serde::{Deserialize, Serialize};

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ApiSettings {
	pub timeout: u64,
	pub max_retries: u32,
	pub base_url: String,
}

impl Default for ApiSettings {
	fn default() -> Self {
		Self {
			timeout: 30,
			max_retries: 3,
			base_url: "http://localhost:8080".to_string(),
		}
	}
}

impl SettingsFragment for ApiSettings {
	type Accessor = ();

	fn section() -> &'static str { "api" }
}

Composing with the #[settings] Macro

use reinhardt::settings;
use reinhardt::conf::CacheSettings;
use crate::config::api_settings::ApiSettings;

#[settings(api: ApiSettings | cache: CacheSettings)]
pub struct ProjectSettings;

Adding to TOML Files

settings/base.toml:

[api]
timeout = 30
max_retries = 3
base_url = "http://localhost:8080"

settings/local.toml:

[api]
timeout = 60

Advanced Usage

Using Multiple File Sources

pub fn get_settings() -> ProjectSettings {
	SettingsBuilder::new()
		.add_source(LowPriorityEnvSource::new().with_prefix("REINHARDT_"))
		.add_source(TomlFileSource::new("settings/base.toml"))
		.add_source(TomlFileSource::new("settings/database.toml"))
		.add_source(TomlFileSource::new("settings/cache.toml"))
		.add_source(TomlFileSource::new("settings/local.toml"))
		.build()
		.expect("Failed to build settings")
		.into_typed()
		.expect("Failed to convert settings")
}

Using JSON Format

use reinhardt::settings::sources::JsonFileSource;

pub fn get_settings() -> ProjectSettings {
	SettingsBuilder::new()
		.add_source(JsonFileSource::new("settings/base.json"))
		.add_source(JsonFileSource::new("settings/local.json"))
		.build()
		.expect("Failed to build settings")
		.into_typed()
		.expect("Failed to convert settings")
}

Implementing Custom Sources

use reinhardt::settings::sources::{ConfigSource, SourceError};
use indexmap::IndexMap;
use serde_json::Value;

struct RemoteConfigSource {
	url: String,
}

impl ConfigSource for RemoteConfigSource {
	fn load(&self) -> Result<IndexMap<String, Value>, SourceError> {
		// Implementation to fetch configuration from remote server
		// Example: HTTP request to fetch JSON configuration
		todo!("Implement remote config loading - fetch from {}", self.url)
	}

	fn priority(&self) -> u8 {
		// Custom priority between TOML files and high-priority env vars
		// 0 = lowest, 100 = highest
		// 75 = higher than TOML (50) but lower than EnvSource (100)
		75
	}

	fn description(&self) -> String {
		format!("Remote configuration from: {}", self.url)
	}
}

Usage:

pub fn get_settings() -> ProjectSettings {
	SettingsBuilder::new()
		.add_source(DefaultSource::new())
		.add_source(TomlFileSource::new("settings/base.toml"))
		.add_source(RemoteConfigSource { url: "https://config.example.com/api/settings".to_string() })
		.build()
		.expect("Failed to build settings")
		.into_typed()
		.expect("Failed to convert settings")
}
// Priority order: RemoteConfigSource (75) > TomlFileSource (50) > DefaultSource (0)

Troubleshooting

Issue 1: Configuration File Not Found

Error:

Failed to build settings: Source error in 'TOML file: settings/base.toml'

Cause:

  • settings/ directory doesn't exist
  • Required TOML files haven't been created

Solution:

ls settings/
# Verify base.toml, local.toml, etc. exist

# If they don't exist, copy from example files
cp settings/base.example.toml settings/base.toml

Issue 2: TOML Syntax Error

Error:

Failed to build settings: Parse error

Cause:

  • Incorrect TOML syntax
  • Invalid quotes
  • Duplicate section names

Solution:

# Use TOML validation tool
cargo install toml-cli
toml get settings/base.toml .

# Use online validation tool
# https://www.toml-lint.com/

Issue 3: Type Conversion Error

Error:

Failed to deserialize key 'debug': invalid type

Cause:

  • Setting value type doesn't match expected type

Solution:

# Correct types
debug = true          # Boolean
port = 5432           # Integer
timeout = 30.5        # Float
name = "mydb"         # String

# Wrong types
debug = "true"        # String (Boolean expected)
port = "5432"         # String (Integer expected)

Issue 4: Missing Required Field

Error:

Failed to convert to ProjectSettings: missing field 'secret_key'

Cause:

  • Required field not defined in TOML file

Solution:

# Add required field to base.toml
secret_key = "your-secret-key-here"

Issue 5: Environment Variables Not Working

Cause:

  • TOML files have higher priority than environment variables
  • Environment variable name is incorrect

Solution:

# 1. Remove the setting from TOML file (if you want environment variable to take priority)

# 2. Verify environment variable name
echo $REINHARDT_DATABASE_HOST

# 3. Use correct prefix
export REINHARDT_DATABASE_HOST=localhost  # Correct
export DATABASE_HOST=localhost            # No prefix

Practical Examples

Example 1: Multi-Tenant Configuration

# base.toml
[[tenants]]
name = "tenant1"
database = "tenant1_db"
schema = "public"

[[tenants]]
name = "tenant2"
database = "tenant2_db"
schema = "public"
#[derive(Debug, Deserialize)]
pub struct TenantConfig {
	pub name: String,
	pub database: String,
	pub schema: String,
}

Example 2: Feature Flag Management

Note: This example shows runtime feature flags (application settings). For compile-time feature flags (Cargo features), see the Feature Flags Guide.

# base.toml
[features]
enable_graphql = false
enable_websockets = false
enable_admin_panel = true
enable_api_versioning = true

# local.toml (all features enabled during development)
[features]
enable_graphql = true
enable_websockets = true

Example 3: External Service Configuration

# base.toml
[services.redis]
host = "localhost"
port = 6379
db = 0

[services.rabbitmq]
host = "localhost"
port = 5672
vhost = "/"

[services.elasticsearch]
hosts = ["http://localhost:9200"]

Performance and Best Practices

1. Initialize Settings Only Once

use std::sync::LazyLock;

static SETTINGS: LazyLock<ProjectSettings> = LazyLock::new(|| {
	get_settings()
});

// Settings are loaded only once, even if accessed multiple times
// Access from anywhere in your application:
// SETTINGS.core.debug
// SETTINGS.core.secret_key

2. Settings Validation

Fragments support profile-based validation via the SettingsFragment::validate() method. Validation runs automatically during into_typed() when a profile is set.

You can also add application-level validation:

pub fn get_settings() -> ProjectSettings {
	let settings: ProjectSettings = SettingsBuilder::new()
		// ... add sources ...
		.build()
		.expect("Failed to build settings")
		.into_typed()
		.expect("Failed to convert settings");
	// Fragment-level validation has already run at this point.

	// Additional application-level validation
	validate_settings(&settings).expect("Invalid settings");

	settings
}

fn validate_settings(settings: &ProjectSettings) -> Result<(), String> {
	if settings.core.secret_key == "CHANGE_THIS_IN_PRODUCTION" {
		return Err("Secret key not set!".to_string());
	}

	Ok(())
}

3. Type-Safe Settings Access

// Access via struct field (type-safe)
let debug = settings.core.debug;
let secret = settings.core.secret_key.clone();

Summary

Reinhardt's settings system is:

  • Flexible: Supports multiple sources including TOML, JSON, and environment variables
  • Composable: Build your settings from reusable fragments with #[settings]
  • Secure: Protects sensitive information with .gitignore, type-safe access
  • Efficient: No recompilation required, settings loaded only once
  • Environment-Aware: Separates configuration for local, staging, production, etc.
  • Extensible: Easy to add custom sources and configuration items

Next Steps


Questions or feedback: https://github.com/kent8192/reinhardt-web/issues