Quick.Options.pas 20 KB

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