1
+ use std:: {
2
+ fs,
3
+ path:: { Path , PathBuf } ,
4
+ } ;
5
+
1
6
use futures:: FutureExt ;
2
7
use serde:: { Deserialize , Serialize } ;
3
8
use snafu:: Snafu ;
@@ -6,6 +11,7 @@ use tide_disco::{
6
11
method:: { ReadState , WriteState } ,
7
12
Api , StatusCode ,
8
13
} ;
14
+ use toml:: { map:: Entry , Value } ;
9
15
use vbs:: version:: StaticVersionType ;
10
16
11
17
use crate :: state:: UpdateSolverState ;
@@ -27,18 +33,38 @@ impl tide_disco::Error for SolverError {
27
33
}
28
34
}
29
35
30
- pub fn define_api < State , SolverError , VERSION > (
36
+ pub struct ApiOptions {
37
+ pub api_path : Option < PathBuf > ,
38
+
39
+ /// Additional API specification files to merge with `availability-api-path`.
40
+ ///
41
+ /// These optional files may contain route definitions for application-specific routes that have
42
+ /// been added as extensions to the basic availability API.
43
+ pub extensions : Vec < toml:: Value > ,
44
+ }
45
+
46
+ impl Default for ApiOptions {
47
+ fn default ( ) -> Self {
48
+ Self {
49
+ api_path : None ,
50
+ extensions : vec ! [ ] ,
51
+ }
52
+ }
53
+ }
54
+
55
+ pub fn define_api < State , VERSION > (
56
+ options : ApiOptions ,
31
57
) -> Result < Api < State , SolverError , VERSION > , ApiError >
32
58
where
33
59
VERSION : StaticVersionType + ' static ,
34
60
State : ' static + Send + Sync + ReadState + WriteState ,
35
61
<State as ReadState >:: State : Send + Sync + UpdateSolverState ,
36
- SolverError : ' static ,
37
62
{
38
- let api_toml = toml:: from_str :: < toml:: Value > ( include_str ! ( "../api/solver.toml" ) )
39
- . expect ( "API file is not valid toml" ) ;
40
-
41
- let mut api = Api :: new ( api_toml) ?;
63
+ let mut api = load_api :: < State , SolverError , VERSION > (
64
+ options. api_path . as_ref ( ) ,
65
+ include_str ! ( "../api/solver.toml" ) ,
66
+ options. extensions . clone ( ) ,
67
+ ) ?;
42
68
43
69
// TODO ED: We need to fill these in with the appropriate logic later
44
70
api. post ( "submit_bid" , |_req, _state| {
61
87
} ) ?;
62
88
Ok ( api)
63
89
}
90
+
91
+ pub ( crate ) fn load_api < State : ' static , Error : ' static , Ver : StaticVersionType + ' static > (
92
+ path : Option < impl AsRef < Path > > ,
93
+ default : & str ,
94
+ extensions : impl IntoIterator < Item = Value > ,
95
+ ) -> Result < Api < State , Error , Ver > , ApiError > {
96
+ let mut toml = match path {
97
+ Some ( path) => load_toml ( path. as_ref ( ) ) ?,
98
+ None => toml:: from_str ( default) . map_err ( |err| ApiError :: CannotReadToml {
99
+ reason : err. to_string ( ) ,
100
+ } ) ?,
101
+ } ;
102
+ for extension in extensions {
103
+ merge_toml ( & mut toml, extension) ;
104
+ }
105
+ Api :: new ( toml)
106
+ }
107
+
108
+ fn merge_toml ( into : & mut Value , from : Value ) {
109
+ if let ( Value :: Table ( into) , Value :: Table ( from) ) = ( into, from) {
110
+ for ( key, value) in from {
111
+ match into. entry ( key) {
112
+ Entry :: Occupied ( mut entry) => merge_toml ( entry. get_mut ( ) , value) ,
113
+ Entry :: Vacant ( entry) => {
114
+ entry. insert ( value) ;
115
+ }
116
+ }
117
+ }
118
+ }
119
+ }
120
+
121
+ fn load_toml ( path : & Path ) -> Result < Value , ApiError > {
122
+ let bytes = fs:: read ( path) . map_err ( |err| ApiError :: CannotReadToml {
123
+ reason : err. to_string ( ) ,
124
+ } ) ?;
125
+ let string = std:: str:: from_utf8 ( & bytes) . map_err ( |err| ApiError :: CannotReadToml {
126
+ reason : err. to_string ( ) ,
127
+ } ) ?;
128
+ toml:: from_str ( string) . map_err ( |err| ApiError :: CannotReadToml {
129
+ reason : err. to_string ( ) ,
130
+ } )
131
+ }
0 commit comments