Quick.Options.pas 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678
  1. { ***************************************************************************
  2. Copyright (c) 2015-2020 Kike Pérez
  3. Unit : Quick.Options
  4. Description : Configuration group settings
  5. Author : Kike Pérez
  6. Version : 1.0
  7. Created : 18/10/2019
  8. Modified : 27/02/2020
  9. This file is part of QuickLib: https://github.com/exilon/QuickLib
  10. ***************************************************************************
  11. Licensed under the Apache License, Version 2.0 (the "License");
  12. you may not use this file except in compliance with the License.
  13. You may obtain a copy of the License at
  14. http://www.apache.org/licenses/LICENSE-2.0
  15. Unless required by applicable law or agreed to in writing, software
  16. distributed under the License is distributed on an "AS IS" BASIS,
  17. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  18. See the License for the specific language governing permissions and
  19. limitations under the License.
  20. *************************************************************************** }
  21. unit Quick.Options;
  22. {$i QuickLib.inc}
  23. interface
  24. uses
  25. Classes,
  26. RTTI,
  27. System.TypInfo,
  28. System.SysUtils,
  29. System.Generics.Collections,
  30. Quick.RTTI.Utils,
  31. Quick.Commons,
  32. Quick.FileMonitor;
  33. type
  34. Required = class(TCustomAttribute);
  35. TValidationCustomAttribute = class(TCustomAttribute)
  36. protected
  37. fErrorMsg : string;
  38. public
  39. property ErrorMsg : string read fErrorMsg write fErrorMsg;
  40. end;
  41. Range = class(TValidationCustomAttribute)
  42. private
  43. fRangeMin : Double;
  44. fRangeMax : Double;
  45. public
  46. constructor Create(aMin, aMax : Integer; const aErrorMsg : string = ''); overload;
  47. constructor Create(aMin, aMax : Double; const aErrorMsg : string = ''); overload;
  48. property Min : Double read fRangeMin write fRangeMax;
  49. property Max : Double read fRangeMax write fRangeMax;
  50. end;
  51. StringLength = class(TValidationCustomAttribute)
  52. private
  53. fMaxLength : Integer;
  54. public
  55. constructor Create(aMaxLength : Integer; const aErrorMsg : string = '');
  56. property MaxLength : Integer read fMaxLength write fMaxLength;
  57. end;
  58. TOptions = class;
  59. TConfigureOptionsProc<T : TOptions> = reference to procedure(aOptions : T);
  60. IOptionsValidator = interface
  61. ['{C6A09F78-8E34-4689-B943-83620437B9EF}']
  62. procedure ValidateOptions;
  63. end;
  64. TOptions = class(TInterfacedObject,IOptionsValidator)
  65. private
  66. fName : string;
  67. fHideOptions : Boolean;
  68. procedure DoValidateOptions; virtual;
  69. public
  70. constructor Create; virtual;
  71. property Name : string read fName write fName;
  72. property HideOptions : Boolean read fHideOptions write fHideOptions;
  73. procedure DefaultValues; virtual;
  74. function ConfigureOptions<T : TOptions>(aOptionsFunc : TConfigureOptionsProc<T>) : IOptionsValidator;
  75. procedure ValidateOptions;
  76. end;
  77. TOptionsValidator = class(TInterfacedObject,IOptionsValidator)
  78. private
  79. fOptions : TOptions;
  80. public
  81. constructor Create(aOptions : TOptions);
  82. procedure ValidateRequired(const aInstance : TObject; aProperty: TRttiProperty);
  83. procedure ValidateStringLength(const aInstance : TObject; aProperty: TRttiProperty; aValidation : StringLength);
  84. procedure ValidateRange(const aInstance : TObject; aProperty: TRttiProperty; aValidation : Range);
  85. procedure ValidateObject(aObj : TObject);
  86. procedure ValidateArray(aValue : TValue);
  87. procedure ValidateOptions;
  88. end;
  89. TOptions<T : TOptions> = record
  90. private
  91. fOptions : T;
  92. public
  93. constructor Create(aOptions : T);
  94. function ConfigureOptions(aOptionsFunc : TConfigureOptionsProc<T>) : IOptionsValidator;
  95. end;
  96. IOptions<T : TOptions> = interface
  97. ['{2779F946-2692-4F74-88AD-F35F5137057A}']
  98. function GetSectionValue : T;
  99. property Value : T read GetSectionValue;
  100. end;
  101. TOptionsClass = class of TOptions;
  102. IOptionsContainer = interface
  103. ['{A593C8BB-53CF-4AA4-9641-BF974E45CBD1}']
  104. function AddSection(aOption : TOptionsClass; const aOptionsName : string = '') : TOptions;
  105. function GetOptions(aOptionClass : TOptionsClass): TOptions;
  106. function GetSection(aOptionsSection : TOptionsClass; var vOptions : TOptions) : Boolean; overload;
  107. procedure AddOption(aOption : TOptions);
  108. function ExistsSection(aOption : TOptionsClass; const aSectionName : string = '') : Boolean;
  109. end;
  110. TSectionList = TObjectList<TOptions>;
  111. IOptionsSerializer = interface
  112. ['{7DECE203-4AAE-4C9D-86C8-B3D583DF7C8B}']
  113. function Load(const aFilename : string; aSections : TSectionList; aFailOnSectionNotExists : Boolean) : Boolean;
  114. function LoadSection(const aFilename : string; aSections : TSectionList; aOptions: TOptions) : Boolean;
  115. procedure Save(const aFilename : string; aSections : TSectionList);
  116. function GetFileSectionNames(const aFilename : string; out oSections : TArray<string>) : Boolean;
  117. end;
  118. TOptionsSerializer = class(TInterfacedObject,IOptionsSerializer)
  119. public
  120. function Load(const aFilename : string; aSections : TSectionList; aFailOnSectionNotExists : Boolean) : Boolean; virtual; abstract;
  121. function LoadSection(const aFilename : string; aSections : TSectionList; aOptions: TOptions) : Boolean; virtual; abstract;
  122. procedure Save(const aFilename : string; aSections : TSectionList); virtual; abstract;
  123. function GetFileSectionNames(const aFilename : string; out oSections : TArray<string>) : Boolean; virtual; abstract;
  124. end;
  125. TFileModifiedEvent = reference to procedure;
  126. TLoadConfigEvent = reference to procedure;
  127. TOptionValue<T : TOptions> = class(TInterfacedObject,IOptions<T>)
  128. private
  129. fValue : T;
  130. function GetSectionValue : T;
  131. public
  132. constructor Create(aValue : T);
  133. property Value : T read GetSectionValue;
  134. end;
  135. TOptionsContainer = class(TInterfacedObject,IOptionsContainer)
  136. private
  137. fFilename : string;
  138. fSerializer : IOptionsSerializer;
  139. fSections : TSectionList;
  140. fFileMonitor : TFileMonitor;
  141. fOnFileModified : TFileModifiedEvent;
  142. fLoaded : Boolean;
  143. fReloadIfFileChanged : Boolean;
  144. fOnConfigLoaded : TLoadConfigEvent;
  145. fOnConfigReloaded : TLoadConfigEvent;
  146. procedure CreateFileMonitor;
  147. procedure FileModifiedNotify(MonitorNotify : TMonitorNotify);
  148. procedure SetReloadIfFileChanged(const Value: Boolean);
  149. function GetOptions(aOptionClass : TOptionsClass): TOptions; overload;
  150. function GetOptions(aIndex : Integer) : TOptions; overload;
  151. function GetSection(aOptionsSection : TOptionsClass; var vOptions : TOptions) : Boolean; overload;
  152. public
  153. constructor Create(const aFilename : string; aOptionsSerializer : IOptionsSerializer; aReloadIfFileChanged : Boolean = False);
  154. destructor Destroy; override;
  155. property FileName : string read fFilename write fFilename;
  156. property ReloadIfFileChanged : Boolean read fReloadIfFileChanged write SetReloadIfFileChanged;
  157. property IsLoaded : Boolean read fLoaded;
  158. function ExistsSection(aOption : TOptionsClass; const aSectionName : string = '') : Boolean; overload;
  159. function ExistsSection<T : TOptions>(const aSectionName : string = '') : Boolean; overload;
  160. property OnFileModified : TFileModifiedEvent read fOnFileModified write fOnFileModified;
  161. property OnConfigLoaded : TLoadConfigEvent read fOnConfigLoaded write fOnConfigLoaded;
  162. property OnConfigReloaded : TLoadConfigEvent read fOnConfigReloaded write fOnConfigReloaded;
  163. property Items[aOptionClass : TOptionsClass] : TOptions read GetOptions; default;
  164. property Items[aIndex : Integer] : TOptions read GetOptions; default;
  165. function AddSection(aOption : TOptionsClass; const aSectionName : string = '') : TOptions; overload;
  166. function AddSection<T : TOptions>(const aSectionName : string = '') : TOptions<T>; overload;
  167. procedure AddOption(aOption : TOptions);
  168. function GetSectionInterface<T : TOptions> : IOptions<T>;
  169. function GetSection<T : TOptions>(const aSectionName : string = '') : T; overload;
  170. function GetFileSectionNames(out oSections : TArray<string>) : Boolean;
  171. function Count : Integer;
  172. procedure Load(aFailOnSectionNotExists : Boolean = False);
  173. procedure LoadSection(aOptions : TOptions);
  174. procedure Save;
  175. end;
  176. IOptionsBuilder<T : TOptions> = interface
  177. ['{1A1DC9A9-7F2D-4CC4-A772-6C7DBAB34424}']
  178. function Options : T;
  179. end;
  180. TOptionsBuilder<T : TOptions> = class(TInterfacedObject,IOptionsBuilder<T>)
  181. protected
  182. fOptions : T;
  183. public
  184. constructor Create;
  185. function Options : T;
  186. end;
  187. EOptionConfigureError = class(Exception);
  188. EOptionLoadError = class(Exception);
  189. EOptionSaveError = class(Exception);
  190. EOptionValidationError = class(Exception);
  191. implementation
  192. { TOptionsContainer }
  193. constructor TOptionsContainer.Create(const aFilename : string; aOptionsSerializer : IOptionsSerializer; aReloadIfFileChanged : Boolean = False);
  194. begin
  195. fSerializer := aOptionsSerializer;
  196. fSections := TSectionList.Create(False);
  197. fFilename := aFilename;
  198. fLoaded := False;
  199. fReloadIfFileChanged := aReloadIfFileChanged;
  200. if aReloadIfFileChanged then CreateFileMonitor;
  201. end;
  202. procedure TOptionsContainer.CreateFileMonitor;
  203. begin
  204. fFileMonitor := TQuickFileMonitor.Create;
  205. fFileMonitor.FileName := fFilename;
  206. fFileMonitor.Interval := 2000;
  207. fFileMonitor.Notifies := [TMonitorNotify.mnFileModified];
  208. fFileMonitor.OnFileChange := FileModifiedNotify;
  209. fFileMonitor.Enabled := True;
  210. end;
  211. destructor TOptionsContainer.Destroy;
  212. var
  213. option : TOptions;
  214. begin
  215. if Assigned(fFileMonitor) then fFileMonitor.Free;
  216. fSerializer := nil;
  217. for option in fSections do
  218. begin
  219. if option.RefCount = 0 then option.Free;
  220. end;
  221. fSections.Free;
  222. inherited;
  223. end;
  224. function TOptionsContainer.ExistsSection(aOption: TOptionsClass;const aSectionName: string): Boolean;
  225. var
  226. option : TOptions;
  227. begin
  228. Result := False;
  229. for option in fSections do
  230. begin
  231. if CompareText(option.ClassName,aOption.ClassName) = 0 then
  232. begin
  233. if (not aSectionName.IsEmpty) and (CompareText(option.Name,aSectionName) = 0) then Exit(True);
  234. end;
  235. end;
  236. end;
  237. function TOptionsContainer.ExistsSection<T>(const aSectionName: string): Boolean;
  238. begin
  239. Result := GetSection<T>(aSectionName) <> nil;
  240. end;
  241. procedure TOptionsContainer.FileModifiedNotify(MonitorNotify: TMonitorNotify);
  242. begin
  243. if MonitorNotify = TMonitorNotify.mnFileModified then
  244. begin
  245. if Assigned(fOnFileModified) then fOnFileModified;
  246. if fReloadIfFileChanged then
  247. begin
  248. Load(False);
  249. end;
  250. end;
  251. end;
  252. procedure TOptionsContainer.AddOption(aOption: TOptions);
  253. begin
  254. if aOption.Name.IsEmpty then aOption.Name := Copy(aOption.ClassName,2,aOption.ClassName.Length);
  255. fSections.Add(aOption);
  256. end;
  257. function TOptionsContainer.AddSection(aOption : TOptionsClass; const aSectionName : string = '') : TOptions;
  258. var
  259. option : TOptions;
  260. begin
  261. //if section already exists, returns it
  262. option := Self.GetOptions(aOption);
  263. if option <> nil then Exit(option);
  264. option := aOption.Create;
  265. if aSectionName.IsEmpty then option.Name := Copy(aOption.ClassName,2,aOption.ClassName.Length)
  266. else option.Name := aSectionName;
  267. fSections.Add(option);
  268. Result := option;
  269. end;
  270. function TOptionsContainer.AddSection<T>(const aSectionName: string): TOptions<T>;
  271. var
  272. option : TOptions;
  273. begin
  274. //if section already exists, returns it
  275. option := Self.GetSection<T>(aSectionName);
  276. if option <> nil then Exit(TOptions<T>(option));
  277. //new section
  278. option := TRTTI.CreateInstance<T>;
  279. if aSectionName.IsEmpty then option.Name := Copy(T.ClassName,2,T.ClassName.Length)
  280. else option.Name := aSectionName;
  281. fSections.Add(option);
  282. Result.Create(option);
  283. end;
  284. function TOptionsContainer.Count: Integer;
  285. begin
  286. Result := fSections.Count;
  287. end;
  288. function TOptionsContainer.GetFileSectionNames(out oSections : TArray<string>) : Boolean;
  289. begin
  290. Result := fSerializer.GetFileSectionNames(fFilename,oSections);
  291. end;
  292. function TOptionsContainer.GetOptions(aIndex: Integer): TOptions;
  293. begin
  294. Result := fSections[aIndex];
  295. end;
  296. function TOptionsContainer.GetSection(aOptionsSection: TOptionsClass; var vOptions: TOptions): Boolean;
  297. var
  298. option : TOptions;
  299. begin
  300. Result := False;
  301. for option in fSections do
  302. begin
  303. if option is TOptionsClass then
  304. begin
  305. vOptions := option as TOptionsClass;
  306. Exit;
  307. end;
  308. end;
  309. end;
  310. function TOptionsContainer.GetOptions(aOptionClass : TOptionsClass) : TOptions;
  311. var
  312. option : TOptions;
  313. begin
  314. Result := nil;
  315. for option in fSections do
  316. begin
  317. if option is aOptionClass then Result := option as TOptionsClass;
  318. end;
  319. end;
  320. function TOptionsContainer.GetSection<T>(const aSectionName : string = '') : T;
  321. var
  322. option : TOptions;
  323. begin
  324. Result := nil;
  325. for option in fSections do
  326. begin
  327. if option is T then
  328. begin
  329. if (aSectionName.IsEmpty) or (CompareText(option.Name,aSectionName) = 0) then
  330. begin
  331. Result := option as T;
  332. Exit;
  333. end;
  334. end;
  335. end;
  336. end;
  337. function TOptionsContainer.GetSectionInterface<T>: IOptions<T>;
  338. begin
  339. Result := TOptionValue<T>.Create(Self.GetSection<T>);
  340. end;
  341. procedure TOptionsContainer.Load(aFailOnSectionNotExists : Boolean = False);
  342. var
  343. option : TOptions;
  344. begin
  345. if FileExists(fFilename) then
  346. begin
  347. if not fSerializer.Load(fFilename,fSections,aFailOnSectionNotExists) then Save;
  348. if not fLoaded then
  349. begin
  350. fLoaded := True;
  351. if Assigned(fOnConfigLoaded) then fOnConfigLoaded;
  352. end
  353. else if Assigned(fOnConfigReloaded) then fOnConfigReloaded;
  354. end
  355. else
  356. begin
  357. //if not exists file get default values
  358. for option in fSections do option.DefaultValues;
  359. //creates default file
  360. Save;
  361. end;
  362. end;
  363. procedure TOptionsContainer.LoadSection(aOptions : TOptions);
  364. begin
  365. if FileExists(fFilename) then
  366. begin
  367. if not fSerializer.LoadSection(fFilename,fSections,aOptions) then Save;
  368. end;
  369. end;
  370. procedure TOptionsContainer.Save;
  371. var
  372. laststate : Boolean;
  373. begin
  374. //disable filemonitor to avoid detect manual save as a external file change
  375. if fReloadIfFileChanged then
  376. begin
  377. laststate := fFileMonitor.Enabled;
  378. fFileMonitor.Enabled := False;
  379. try
  380. //save config file
  381. fSerializer.Save(fFilename,fSections);
  382. finally
  383. //set last state
  384. Sleep(0);
  385. fFileMonitor.Enabled := laststate;
  386. end;
  387. end
  388. else fSerializer.Save(fFilename,fSections);
  389. end;
  390. procedure TOptionsContainer.SetReloadIfFileChanged(const Value: Boolean);
  391. begin
  392. if Value = fReloadIfFileChanged then Exit;
  393. fReloadIfFileChanged := Value;
  394. if Assigned(fFileMonitor) then fFileMonitor.Free;
  395. if fReloadIfFileChanged then CreateFileMonitor;
  396. end;
  397. { TOptions }
  398. function TOptions.ConfigureOptions<T>(aOptionsFunc: TConfigureOptionsProc<T>): IOptionsValidator;
  399. var
  400. value : TValue;
  401. begin
  402. Result := TOptionsValidator.Create(Self);
  403. if Assigned(aOptionsFunc) then
  404. begin
  405. value := Self;
  406. aOptionsFunc(value.AsType<T>);
  407. end;
  408. end;
  409. constructor TOptions.Create;
  410. begin
  411. fName := '';
  412. fHideOptions := False;
  413. end;
  414. procedure TOptions.DefaultValues;
  415. begin
  416. //nothing
  417. end;
  418. procedure TOptions.DoValidateOptions;
  419. var
  420. ivalidator : IOptionsValidator;
  421. begin
  422. ivalidator := TOptionsValidator.Create(Self);
  423. ivalidator.ValidateOptions;
  424. end;
  425. procedure TOptions.ValidateOptions;
  426. begin
  427. try
  428. DoValidateOptions;
  429. except
  430. on E : Exception do
  431. begin
  432. raise EOptionConfigureError.CreateFmt('Validation Options Error : %s',[e.Message]);
  433. end;
  434. end;
  435. end;
  436. { TOptionsValidator }
  437. procedure TOptionsValidator.ValidateObject(aObj : TObject);
  438. var
  439. ctx : TRttiContext;
  440. rtype : TRttiType;
  441. rprop : TRttiProperty;
  442. attrib : TCustomAttribute;
  443. rvalue : TValue;
  444. begin
  445. rtype := ctx.GetType(aObj.ClassInfo);
  446. for rprop in rtype.GetProperties do
  447. begin
  448. //check only published properties
  449. if rprop.Visibility = TMemberVisibility.mvPublished then
  450. begin
  451. //check validation option attributes
  452. for attrib in rprop.GetAttributes do
  453. begin
  454. if attrib is Required then ValidateRequired(aObj,rprop)
  455. else if attrib is StringLength then ValidateStringLength(aObj,rprop,StringLength(attrib))
  456. else if attrib is Range then ValidateRange(aObj,rprop,Range(attrib));
  457. end;
  458. rvalue := rprop.GetValue(aObj);
  459. if not rvalue.IsEmpty then
  460. begin
  461. case rvalue.Kind of
  462. tkClass : ValidateObject(rvalue.AsObject);
  463. tkDynArray : ValidateArray(rvalue);
  464. end;
  465. end;
  466. end;
  467. end;
  468. end;
  469. constructor TOptionsValidator.Create(aOptions: TOptions);
  470. begin
  471. fOptions := aOptions;
  472. end;
  473. procedure TOptionsValidator.ValidateOptions;
  474. begin
  475. ValidateObject(fOptions);
  476. end;
  477. procedure TOptionsValidator.ValidateArray(aValue : TValue);
  478. type
  479. PPByte = ^PByte;
  480. var
  481. ctx : TRttiContext;
  482. rDynArray : TRttiDynamicArrayType;
  483. itvalue : TValue;
  484. i : Integer;
  485. begin
  486. rDynArray := ctx.GetType(aValue.TypeInfo) as TRTTIDynamicArrayType;
  487. for i := 0 to aValue.GetArrayLength - 1 do
  488. begin
  489. TValue.Make(PPByte(aValue.GetReferenceToRawData)^ + rDynArray.ElementType.TypeSize * i, rDynArray.ElementType.Handle,itvalue);
  490. if not itvalue.IsEmpty then
  491. begin
  492. case itvalue.Kind of
  493. tkClass : ValidateObject(itvalue.AsObject);
  494. tkDynArray : ValidateArray(itvalue);
  495. end;
  496. end;
  497. end;
  498. end;
  499. procedure TOptionsValidator.ValidateRange(const aInstance : TObject; aProperty: TRttiProperty; aValidation : Range);
  500. var
  501. value : TValue;
  502. msg : string;
  503. begin
  504. value := aProperty.GetValue(aInstance);
  505. if not value.IsEmpty then
  506. begin
  507. if value.Kind = tkFloat then
  508. begin
  509. if (value.AsExtended < aValidation.Min) or (value.AsExtended > aValidation.Max) then
  510. begin
  511. if aValidation.ErrorMsg.IsEmpty then msg := Format('Option %s "%s.%s" exceeds predefined range (%2f - %2f)',[fOptions.Name,aInstance.ClassName,aProperty.Name,aValidation.Min,aValidation.Max])
  512. else msg := aValidation.ErrorMsg;
  513. raise EOptionValidationError.Create(msg);
  514. end;
  515. end
  516. else if value.Kind in [tkInteger,tkInt64] then
  517. begin
  518. if (value.AsInt64 < aValidation.Min) or (value.AsInt64 > aValidation.Max) then
  519. begin
  520. if aValidation.ErrorMsg.IsEmpty then msg := Format('Option %s "%s.%s" exceeds predefined range (%d - %d)',[fOptions.Name,aInstance.ClassName,aProperty.Name,Round(aValidation.Min),Round(aValidation.Max)])
  521. else msg := aValidation.ErrorMsg;
  522. raise EOptionValidationError.Create(msg);
  523. end;
  524. end;
  525. end;
  526. end;
  527. procedure TOptionsValidator.ValidateRequired(const aInstance : TObject; aProperty: TRttiProperty);
  528. begin
  529. if aProperty.GetValue(aInstance).IsEmpty then raise EOptionValidationError.CreateFmt('Option %s "%s.%s" is required',[fOptions.Name,aInstance.ClassName,aProperty.Name]);
  530. end;
  531. procedure TOptionsValidator.ValidateStringLength(const aInstance : TObject; aProperty: TRttiProperty; aValidation : StringLength);
  532. var
  533. value : TValue;
  534. msg : string;
  535. begin
  536. value := aProperty.GetValue(aInstance);
  537. if (not value.IsEmpty) and (value.AsString.Length > aValidation.MaxLength) then
  538. begin
  539. if aValidation.ErrorMsg.IsEmpty then msg := Format('Option %s "%s.%s" exceeds max length (%d)',[fOptions.Name,aInstance.ClassName,aProperty.Name,aValidation.MaxLength])
  540. else msg := aValidation.ErrorMsg;
  541. raise EOptionValidationError.Create(msg);
  542. end;
  543. end;
  544. { Range }
  545. constructor Range.Create(aMin, aMax: Integer; const aErrorMsg : string = '');
  546. begin
  547. fRangeMin := aMin;
  548. fRangeMax := aMax;
  549. fErrorMsg := aErrorMsg;
  550. end;
  551. constructor Range.Create(aMin, aMax: Double; const aErrorMsg: string);
  552. begin
  553. fRangeMin := aMin;
  554. fRangeMax := aMax;
  555. fErrorMsg := aErrorMsg;
  556. end;
  557. { StringLength }
  558. constructor StringLength.Create(aMaxLength: Integer; const aErrorMsg : string = '');
  559. begin
  560. fMaxLength := aMaxLength;
  561. fErrorMsg := aErrorMsg;
  562. end;
  563. { TOptionValue<T> }
  564. constructor TOptionValue<T>.Create(aValue: T);
  565. begin
  566. fValue := aValue;
  567. end;
  568. function TOptionValue<T>.GetSectionValue: T;
  569. begin
  570. Result := fValue;
  571. end;
  572. { TOptions<T> }
  573. function TOptions<T>.ConfigureOptions(aOptionsFunc: TConfigureOptionsProc<T>): IOptionsValidator;
  574. begin
  575. if Assigned(aOptionsFunc) then Result := fOptions.ConfigureOptions<T>(aOptionsFunc)
  576. else Result := TOptionsValidator.Create(fOptions);
  577. end;
  578. constructor TOptions<T>.Create(aOptions: T);
  579. begin
  580. fOptions := aOptions;
  581. end;
  582. { TOptionsBuilder<T> }
  583. constructor TOptionsBuilder<T>.Create;
  584. begin
  585. fOptions := (PTypeInfo(TypeInfo(T)).TypeData.ClassType.Create) as T;
  586. end;
  587. function TOptionsBuilder<T>.Options: T;
  588. begin
  589. Result := fOptions;
  590. end;
  591. end.