Prechádzať zdrojové kódy

new TOptions and IOptions<TOptions>

Exilon 5 rokov pred
rodič
commit
a6dbee082f

+ 124 - 0
Quick.Options.Serializer.Json.pas

@@ -0,0 +1,124 @@
+{ ***************************************************************************
+
+  Copyright (c) 2015-2019 Kike Pérez
+
+  Unit        : Quick.Options.Serializer.Json
+  Description : Configuration groups Json Serializer
+  Author      : Kike Pérez
+  Version     : 1.0
+  Created     : 18/10/2019
+  Modified    : 22/10/2019
+
+  This file is part of QuickLib: https://github.com/exilon/QuickLib
+
+ ***************************************************************************
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+ *************************************************************************** }
+
+unit Quick.Options.Serializer.Json;
+
+{$i QuickLib.inc}
+
+interface
+
+uses
+  System.SysUtils,
+  System.IOUtils,
+  System.Generics.Collections,
+  System.Json,
+  Quick.Commons,
+  Quick.JSON.Utils,
+  Quick.Json.Serializer,
+  Quick.Options;
+
+type
+
+  TJsonOptionsSerializer = class(TOptionsSerializer)
+  private
+    fSerializer : TRTTIJson;
+  public
+    constructor Create;
+    destructor Destroy; override;
+    procedure Load(const aFilename : string; aSections : TSectionList); override;
+    procedure Save(const aFilename : string; aSections : TSectionList); override;
+  end;
+
+implementation
+
+{ TJsonOptionsSerializer }
+
+constructor TJsonOptionsSerializer.Create;
+begin
+  fSerializer := TRTTIJson.Create(TSerializeLevel.slPublishedProperty,True);
+end;
+
+destructor TJsonOptionsSerializer.Destroy;
+begin
+  fSerializer.Free;
+  inherited;
+end;
+
+procedure TJsonOptionsSerializer.Load(const aFilename : string; aSections : TSectionList);
+var
+  option : TOptions;
+  fileoptions : string;
+  json : TJsonObject;
+  jpair : TJSONPair;
+begin
+  if FileExists(aFilename) then
+  begin
+    //read option file
+    fileoptions := TFile.ReadAllText(aFilename,TEncoding.UTF8);
+    json := TJSONObject.ParseJSONValue(fileoptions) as TJSONObject;
+    for option in aSections do
+    begin
+      jpair := fSerializer.GetJsonPairByName(json,option.Name);
+      if jpair = nil then raise Exception.CreateFmt('Config section "%s" not found',[option.Name]);
+      if jpair.JsonValue <> nil then
+      begin
+        //deserialize option
+        fSerializer.DeserializeObject(option,jpair.JsonValue as TJSONObject);
+        //validate loaded configuration
+        option.ValidateOptions;
+      end;
+    end;
+  end;
+end;
+
+procedure TJsonOptionsSerializer.Save(const aFilename : string; aSections : TSectionList);
+var
+  option : TOptions;
+  fileoptions : string;
+  json : TJSONObject;
+  jpair : TJSONPair;
+begin
+  json := TJSONObject.Create;
+  try
+    for option in aSections do
+    begin
+      //validate configuration before save
+      option.ValidateOptions;
+      //serialize option
+      jpair := fSerializer.Serialize(option.Name,option);
+      json.AddPair(jpair);
+    end;
+    fileoptions := TJsonUtils.JsonFormat(json.ToJSON);
+    TFile.WriteAllText(aFilename,fileoptions);
+  finally
+    json.Free;
+  end;
+end;
+
+end.

+ 123 - 0
Quick.Options.Serializer.Yaml.pas

@@ -0,0 +1,123 @@
+{ ***************************************************************************
+
+  Copyright (c) 2015-2019 Kike Pérez
+
+  Unit        : Quick.Options.Serializer.Yaml
+  Description : Configuration groups Yaml Serializer
+  Author      : Kike Pérez
+  Version     : 1.0
+  Created     : 18/10/2019
+  Modified    : 22/10/2019
+
+  This file is part of QuickLib: https://github.com/exilon/QuickLib
+
+ ***************************************************************************
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+ *************************************************************************** }
+
+unit Quick.Options.Serializer.Yaml;
+
+{$i QuickLib.inc}
+
+interface
+
+uses
+  System.SysUtils,
+  System.IOUtils,
+  System.Generics.Collections,
+  Quick.YAML,
+  Quick.Commons,
+  Quick.YAML.Serializer,
+  Quick.Options;
+
+type
+
+  TYamlOptionsSerializer = class(TOptionsSerializer)
+  private
+    fSerializer : TRTTIYaml;
+  public
+    constructor Create;
+    destructor Destroy; override;
+    procedure Load(const aFilename : string; aSections : TSectionList); override;
+    procedure Save(const aFilename : string; aSections : TSectionList); override;
+  end;
+
+implementation
+
+{ TYamlOptionsSerializer }
+
+constructor TYamlOptionsSerializer.Create;
+begin
+  fSerializer := TRTTIYaml.Create(TSerializeLevel.slPublishedProperty,True);
+end;
+
+destructor TYamlOptionsSerializer.Destroy;
+begin
+  fSerializer.Free;
+  inherited;
+end;
+
+procedure TYamlOptionsSerializer.Load(const aFilename : string; aSections : TSectionList);
+var
+  option : TOptions;
+  fileoptions : string;
+  json : TYamlObject;
+  jpair : TYamlPair;
+begin
+  if FileExists(aFilename) then
+  begin
+    //read option file
+    fileoptions := TFile.ReadAllText(aFilename,TEncoding.UTF8);
+    json := TYamlObject.ParseYAMLValue(fileoptions) as TYamlObject;
+    for option in aSections do
+    begin
+      jpair := fSerializer.GetYamlPairByName(json,option.Name);
+      if jpair.Value <> nil then
+      begin
+        //deserialize option
+        fSerializer.DeserializeObject(option,jpair.Value as TYamlObject);
+        //validate loaded configuration
+        option.ValidateOptions;
+      end;
+    end;
+  end;
+end;
+
+procedure TYamlOptionsSerializer.Save(const aFilename : string; aSections : TSectionList);
+var
+  option : TOptions;
+  fileoptions : string;
+  yaml : TYamlObject;
+  jpair : TYamlPair;
+begin
+  yaml := TYamlObject.Create;
+  try
+    for option in aSections do
+    begin
+      //validate configuration before save
+      option.ValidateOptions;
+      //serialize option
+      jpair := fSerializer.Serialize(option.Name,option);
+      yaml.AddPair(jpair);
+    end;
+    fileoptions := yaml.ToYaml;
+    TFile.WriteAllText(aFilename,fileoptions);
+  finally
+    yaml.Free;
+  end;
+end;
+
+end.
+

+ 552 - 0
Quick.Options.pas

@@ -0,0 +1,552 @@
+{ ***************************************************************************
+
+  Copyright (c) 2015-2019 Kike Pérez
+
+  Unit        : Quick.Options
+  Description : Configuration group settings
+  Author      : Kike Pérez
+  Version     : 1.0
+  Created     : 18/10/2019
+  Modified    : 29/10/2019
+
+  This file is part of QuickLib: https://github.com/exilon/QuickLib
+
+ ***************************************************************************
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+ *************************************************************************** }
+
+unit Quick.Options;
+
+{$i QuickLib.inc}
+
+interface
+
+uses
+  Classes,
+  RTTI,
+  Quick.RTTI.Utils,
+  System.TypInfo,
+  System.SysUtils,
+  System.Generics.Collections,
+  System.Json,
+  Quick.Commons,
+  Quick.FileMonitor;
+
+type
+  Required = class(TCustomAttribute);
+
+  TValidationCustomAttribute = class(TCustomAttribute)
+  protected
+    fErrorMsg : string;
+  public
+    property ErrorMsg : string read fErrorMsg write fErrorMsg;
+  end;
+
+  Range = class(TValidationCustomAttribute)
+  private
+    fRangeMin : Double;
+    fRangeMax : Double;
+  public
+    constructor Create(aMin, aMax : Integer; const aErrorMsg : string = ''); overload;
+    constructor Create(aMin, aMax : Double; const aErrorMsg : string = ''); overload;
+    property Min : Double read fRangeMin write fRangeMax;
+    property Max : Double read fRangeMax write fRangeMax;
+  end;
+
+  StringLength = class(TValidationCustomAttribute)
+  private
+    fMaxLength : Integer;
+  public
+    constructor Create(aMaxLength : Integer; const aErrorMsg : string = '');
+    property MaxLength : Integer read fMaxLength write fMaxLength;
+  end;
+
+  TOptions = class;
+
+  TConfigureOptionsProc<T : TOptions> = reference to procedure(aOptions : T);
+
+  IOptionsValidator = interface
+  ['{C6A09F78-8E34-4689-B943-83620437B9EF}']
+    procedure ValidateOptions;
+  end;
+
+  IOptionsConfigure<T> = interface
+  ['{49258BEB-A21D-4C64-BA71-767B8DBD4D92}']
+    //function ConfigureOptions(aOptionsFunc : TConfigureOptionsProc<T>) : IOptionsValidator;
+  end;
+
+  TOptions = class(TInterfacedObject,IOptionsValidator)
+  private
+    fName : string;
+    procedure ValidateRequired(aProperty : TRttiProperty);
+    procedure ValidateStringLength(aProperty: TRttiProperty; aValidation : StringLength);
+    procedure ValidateRange(aProperty : TRttiProperty; aValidation : Range);
+    procedure DoValidateOptions; virtual;
+  public
+    constructor Create;
+    property Name : string read fName write fName;
+    procedure DefaultValues; virtual; abstract;
+    function ConfigureOptions<T : TOptions>(aOptionsFunc : TConfigureOptionsProc<T>) : IOptionsValidator;
+    procedure ValidateOptions;
+  end;
+
+  TOptions<T : TOptions> = record
+  private
+    fOptions : T;
+  public
+    constructor Create(aOptions : T);
+    function ConfigureOptions(aOptionsFunc : TConfigureOptionsProc<T>) : IOptionsValidator;
+  end;
+
+  IOptions<T : TOptions> = interface
+  ['{2779F946-2692-4F74-88AD-F35F5137057A}']
+    function GetSectionValue : T;
+    property Value : T read GetSectionValue;
+  end;
+
+  TOptionsClass = class of TOptions;
+
+  IOptionsContainer = interface
+  ['{A593C8BB-53CF-4AA4-9641-BF974E45CBD1}']
+    function AddSection(aOption : TOptionsClass; const aOptionsName : string = '') : TOptions;
+    function GetOptions(aOptionClass : TOptionsClass): TOptions;
+    function GetSection(aOptionsSection : TOptionsClass; aOptions : TOptions) : Boolean; overload;
+  end;
+
+  TSectionList = TObjectList<TOptions>;
+
+  IOptionsSerializer = interface
+  ['{7DECE203-4AAE-4C9D-86C8-B3D583DF7C8B}']
+    procedure Load(const aFilename : string; aSections : TSectionList);
+    procedure Save(const aFilename : string; aSections : TSectionList);
+  end;
+
+  TOptionsSerializer = class(TInterfacedObject,IOptionsSerializer)
+  public
+    procedure Load(const aFilename : string; aSections : TSectionList); virtual; abstract;
+    procedure Save(const aFilename : string; aSections : TSectionList); virtual; abstract;
+  end;
+
+  TFileModifiedEvent = reference to procedure;
+  TLoadConfigEvent = reference to procedure;
+
+  TOptionValue<T : TOptions> = class(TInterfacedObject,IOptions<T>)
+  private
+    fValue : T;
+    function GetSectionValue : T;
+  public
+    constructor Create(aValue : T);
+    property Value : T read GetSectionValue;
+  end;
+
+  TOptionsContainer = class(TInterfacedObject,IOptionsContainer)
+  private
+    fFilename : string;
+    fSerializer : IOptionsSerializer;
+    fSections : TSectionList;
+    fFileMonitor : TFileMonitor;
+    fOnFileModified : TFileModifiedEvent;
+    fLoaded : Boolean;
+    fReloadIfFileChanged : Boolean;
+    fOnConfigLoaded : TLoadConfigEvent;
+    fOnConfigReloaded : TLoadConfigEvent;
+    procedure CreateFileMonitor;
+    procedure FileModifiedNotify(MonitorNotify : TMonitorNotify);
+    procedure SetReloadIfFileChanged(const Value: Boolean);
+    function GetOptions(aOptionClass : TOptionsClass): TOptions; overload;
+    function GetOptions(aIndex : Integer) : TOptions; overload;
+    function GetSection(aOptionsSection : TOptionsClass; aOptions : TOptions) : Boolean; overload;
+  public
+    constructor Create(const aFilename : string; aOptionsSerializer : IOptionsSerializer; aReloadIfFileChanged : Boolean = False);
+    destructor Destroy; override;
+    property FileName : string read fFilename write fFilename;
+    property ReloadIfFileChanged : Boolean read fReloadIfFileChanged write SetReloadIfFileChanged;
+    property IsLoaded : Boolean read fLoaded;
+    property OnFileModified : TFileModifiedEvent read fOnFileModified write fOnFileModified;
+    property OnConfigLoaded : TLoadConfigEvent read fOnConfigLoaded write fOnConfigLoaded;
+    property OnConfigReloaded : TLoadConfigEvent read fOnConfigReloaded write fOnConfigReloaded;
+    property Items[aOptionClass : TOptionsClass] : TOptions read GetOptions; default;
+    property Items[aIndex : Integer] : TOptions read GetOptions; default;
+    function AddSection(aOption : TOptionsClass; const aSectionName : string = '') : TOptions; overload;
+    function AddSection<T : TOptions>(const aSectionName : string = '') : TOptions<T>; overload;
+    function GetSectionInterface<T : TOptions> : IOptions<T>;
+    function GetSection<T : TOptions>(const aSectionName : string = '') : T; overload;
+    function Count : Integer;
+    procedure Load;
+    procedure Save;
+  end;
+
+  IOptionsBuilder<T : TOptions> = interface
+  ['{1A1DC9A9-7F2D-4CC4-A772-6C7DBAB34424}']
+    function Options : T;
+  end;
+
+  TOptionsBuilder<T : TOptions> = class(TInterfacedObject,IOptionsBuilder<T>)
+  protected
+    fOptions : T;
+  public
+    constructor Create;
+    function Options : T;
+  end;
+
+  EOptionConfigureError = class(Exception);
+  EOptionLoadError = class(Exception);
+  EOptionSaveError = class(Exception);
+  EOptionValidationError = class(Exception);
+
+implementation
+
+{ TOptionsContainer }
+
+constructor TOptionsContainer.Create(const aFilename : string; aOptionsSerializer : IOptionsSerializer; aReloadIfFileChanged : Boolean = False);
+begin
+  fSerializer := aOptionsSerializer;
+  fSections := TSectionList.Create(True);
+  fFilename := aFilename;
+  fLoaded := False;
+  fReloadIfFileChanged := aReloadIfFileChanged;
+  if aReloadIfFileChanged then CreateFileMonitor;
+end;
+
+procedure TOptionsContainer.CreateFileMonitor;
+begin
+  fFileMonitor := TQuickFileMonitor.Create;
+  fFileMonitor.FileName := fFilename;
+  fFileMonitor.Interval := 2000;
+  fFileMonitor.Notifies := [TMonitorNotify.mnFileModified];
+  fFileMonitor.OnFileChange := FileModifiedNotify;
+  fFileMonitor.Enabled := True;
+end;
+
+destructor TOptionsContainer.Destroy;
+begin
+  if Assigned(fFileMonitor) then fFileMonitor.Free;
+  fSerializer := nil;
+  fSections.Free;
+  inherited;
+end;
+
+procedure TOptionsContainer.FileModifiedNotify(MonitorNotify: TMonitorNotify);
+begin
+  if MonitorNotify = TMonitorNotify.mnFileModified then
+  begin
+    if Assigned(fOnFileModified) then fOnFileModified;
+    if fReloadIfFileChanged then
+    begin
+      Load;
+    end;
+  end;
+end;
+
+function TOptionsContainer.AddSection(aOption : TOptionsClass; const aSectionName : string = '') : TOptions;
+var
+  option : TOptions;
+begin
+  option := aOption.Create;
+  if aSectionName.IsEmpty then option.Name := Copy(aOption.ClassName,2,aOption.ClassName.Length)
+    else option.Name := aSectionName;
+  fSections.Add(option);
+  Result := option;
+end;
+
+function TOptionsContainer.AddSection<T>(const aSectionName: string): TOptions<T>;
+var
+  option : TOptions;
+begin
+  option := TRTTI.CreateInstance<T>;
+  if aSectionName.IsEmpty then option.Name := Copy(T.ClassName,2,T.ClassName.Length)
+    else option.Name := aSectionName;
+  fSections.Add(option);
+  Result.Create(option);
+end;
+
+function TOptionsContainer.Count: Integer;
+begin
+  Result := fSections.Count;
+end;
+
+function TOptionsContainer.GetOptions(aIndex: Integer): TOptions;
+begin
+  Result := fSections[aIndex];
+end;
+
+function TOptionsContainer.GetSection(aOptionsSection: TOptionsClass; aOptions: TOptions): Boolean;
+var
+  option : TOptions;
+begin
+  Result := False;
+  for option in fSections do
+  begin
+    if option is TOptionsClass then
+    begin
+      aOptions := option as TOptionsClass;
+      Exit;
+    end;
+  end;
+end;
+
+function TOptionsContainer.GetOptions(aOptionClass : TOptionsClass) : TOptions;
+var
+  option : TOptions;
+begin
+  Result := nil;
+  for option in fSections do
+  begin
+    if option is TOptionsClass then Result := option as TOptionsClass;
+  end;
+end;
+
+function TOptionsContainer.GetSection<T>(const aSectionName : string = '') : T;
+var
+  option : TOptions;
+begin
+  for option in fSections do
+  begin
+    if option is T then
+    begin
+      if (aSectionName.IsEmpty) or (CompareText(option.Name,aSectionName) = 0) then
+      begin
+        Result := option as T;
+        Exit;
+      end;
+    end;
+  end;
+end;
+
+function TOptionsContainer.GetSectionInterface<T>: IOptions<T>;
+begin
+  Result := TOptionValue<T>.Create(Self.GetSection<T>);
+end;
+
+procedure TOptionsContainer.Load;
+var
+  option : TOptions;
+begin
+  if FileExists(fFilename) then
+  begin
+    fSerializer.Load(fFilename,fSections);
+    if not fLoaded then
+    begin
+      fLoaded := True;
+      if Assigned(fOnConfigLoaded) then fOnConfigLoaded;
+    end
+    else if Assigned(fOnConfigReloaded) then fOnConfigReloaded;
+  end
+  else
+  begin
+    //if not exists file get default values
+    for option in fSections do option.DefaultValues;
+    //creates default file
+    Save;
+  end;
+end;
+
+procedure TOptionsContainer.Save;
+var
+  laststate : Boolean;
+begin
+  //disable filemonitor to avoid detect manual save as a external file change
+  laststate := fFileMonitor.Enabled;
+  fFileMonitor.Enabled := False;
+  try
+    //save config file
+    fSerializer.Save(fFilename,fSections);
+  finally
+    //set last state
+    fFileMonitor.Enabled := laststate;
+  end;
+end;
+
+procedure TOptionsContainer.SetReloadIfFileChanged(const Value: Boolean);
+begin
+  if Value = fReloadIfFileChanged then Exit;
+  fReloadIfFileChanged := Value;
+  if Assigned(fFileMonitor) then fFileMonitor.Free;
+  if fReloadIfFileChanged then CreateFileMonitor;
+end;
+
+{ TOptions }
+
+function TOptions.ConfigureOptions<T>(aOptionsFunc: TConfigureOptionsProc<T>): IOptionsValidator;
+var
+  value : TValue;
+begin
+  Result := Self;
+  if Assigned(aOptionsFunc) then
+  begin
+    value := Self;
+    aOptionsFunc(value.AsType<T>);
+  end;
+end;
+
+constructor TOptions.Create;
+begin
+  fName := '';
+end;
+
+procedure TOptions.DoValidateOptions;
+var
+  ctx : TRttiContext;
+  rtype : TRttiType;
+  rprop : TRttiProperty;
+  attrib : TCustomAttribute;
+begin
+  ctx := TRttiContext.Create;
+  try
+    rtype := ctx.GetType(Self.ClassInfo);
+    for rprop in rtype.GetProperties do
+    begin
+      //check only published properties
+      if rprop.Visibility = TMemberVisibility.mvPublished then
+      begin
+        //check validation option attributes
+        for attrib in rprop.GetAttributes do
+        begin
+          if attrib is Required  then ValidateRequired(rprop)
+          else if attrib is StringLength then ValidateStringLength(rprop,StringLength(attrib))
+          else if attrib is Range then ValidateRange(rprop,Range(attrib));
+        end;
+      end;
+    end;
+  finally
+    ctx.Free;
+  end;
+end;
+
+procedure TOptions.ValidateOptions;
+begin
+  try
+    DoValidateOptions;
+  except
+    on E : Exception do
+    begin
+      raise EOptionConfigureError.CreateFmt('Validation Options Error : %s',[e.Message]);
+    end;
+  end;
+end;
+
+procedure TOptions.ValidateRange(aProperty: TRttiProperty; aValidation : Range);
+var
+  value : TValue;
+  msg : string;
+begin
+  value := aProperty.GetValue(Self);
+  if not value.IsEmpty then
+  begin
+    if value.Kind = tkFloat then
+    begin
+      if (value.AsExtended < aValidation.Min) or (value.AsExtended > aValidation.Max) then
+      begin
+        if aValidation.ErrorMsg.IsEmpty then msg := Format('Option "%s.%s" exceeds predefined range (%2f - %2f)',[Self.Name,aProperty.Name,aValidation.Min,aValidation.Max])
+          else msg := aValidation.ErrorMsg;
+        raise EOptionValidationError.Create(msg);
+      end;
+    end
+    else if value.Kind in [tkInteger,tkInt64] then
+    begin
+      if (value.AsInt64 < aValidation.Min) or (value.AsInt64 > aValidation.Max) then
+      begin
+        if aValidation.ErrorMsg.IsEmpty then msg := Format('Option "%s.%s" exceeds predefined range (%d - %d)',[Self.Name,aProperty.Name,Round(aValidation.Min),Round(aValidation.Max)])
+          else msg := aValidation.ErrorMsg;
+        raise EOptionValidationError.Create(msg);
+      end;
+    end;
+  end;
+end;
+
+procedure TOptions.ValidateRequired(aProperty: TRttiProperty);
+begin
+  if aProperty.GetValue(Self).IsEmpty then raise EOptionValidationError.CreateFmt('Option "%s.%s" is required',[Self.Name,aProperty.Name]);
+end;
+
+procedure TOptions.ValidateStringLength(aProperty: TRttiProperty; aValidation : StringLength);
+var
+  value : TValue;
+  msg : string;
+begin
+  value := aProperty.GetValue(Self);
+  if (not value.IsEmpty) and (value.AsString.Length > aValidation.MaxLength) then
+  begin
+    if aValidation.ErrorMsg.IsEmpty then msg := Format('Option "%s.%s" exceeds max length (%d)',[Self.Name,aProperty.Name,aValidation.MaxLength])
+      else msg := aValidation.ErrorMsg;
+
+    raise EOptionValidationError.Create(msg);
+  end;
+end;
+
+{ Range }
+
+constructor Range.Create(aMin, aMax: Integer; const aErrorMsg : string = '');
+begin
+  fRangeMin := aMin;
+  fRangeMax := aMax;
+  fErrorMsg := aErrorMsg;
+end;
+
+constructor Range.Create(aMin, aMax: Double; const aErrorMsg: string);
+begin
+  fRangeMin := aMin;
+  fRangeMax := aMax;
+  fErrorMsg := aErrorMsg;
+end;
+
+{ StringLength }
+
+constructor StringLength.Create(aMaxLength: Integer; const aErrorMsg : string = '');
+begin
+  fMaxLength := aMaxLength;
+  fErrorMsg := aErrorMsg;
+end;
+
+
+{ TOptionValue<T> }
+
+constructor TOptionValue<T>.Create(aValue: T);
+begin
+  fValue := aValue;
+end;
+
+function TOptionValue<T>.GetSectionValue: T;
+begin
+  Result := fValue;
+end;
+
+{ TOptions<T> }
+
+function TOptions<T>.ConfigureOptions(aOptionsFunc: TConfigureOptionsProc<T>): IOptionsValidator;
+begin
+  if Assigned(aOptionsFunc) then Result := fOptions.ConfigureOptions<T>(aOptionsFunc)
+    else Result := fOptions;
+  fOptions._AddRef;
+end;
+
+constructor TOptions<T>.Create(aOptions: T);
+begin
+  fOptions := aOptions;
+end;
+
+{ TOptionsBuilder<T> }
+
+constructor TOptionsBuilder<T>.Create;
+begin
+  fOptions := (PTypeInfo(TypeInfo(T)).TypeData.ClassType.Create) as T;
+end;
+
+function TOptionsBuilder<T>.Options: T;
+begin
+  Result := fOptions;
+end;
+
+end.