• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

getdozer / dozer / 4164814422

pending completion
4164814422

Pull #883

github

GitHub
Merge eae178c73 into 7cb6e2163
Pull Request #883: refactor: Remove unused struct `PipelineDetails`

211 of 211 new or added lines in 12 files covered. (100.0%)

23813 of 35480 relevant lines covered (67.12%)

41873.4 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.11
/dozer-api/src/generator/oapi/generator.rs
1
use super::utils::{
2
    convert_cache_to_oapi_schema, create_contact_info, create_reference_response, create_response,
3
};
4
use crate::errors::GenerationError;
5
use dozer_types::indexmap::{self, IndexMap};
6
use dozer_types::serde_json;
7
use dozer_types::types::IndexDefinition;
8
use dozer_types::{models::api_endpoint::ApiEndpoint, types::FieldType};
9
use openapiv3::*;
10
use serde_json::{json, Value};
11
use tempdir::TempDir;
12

13
pub struct OpenApiGenerator {
14
    schema: dozer_types::types::Schema,
15
    secondary_indexes: Vec<IndexDefinition>,
16
    endpoint: ApiEndpoint,
17
    server_host: Vec<String>,
18
}
19
impl OpenApiGenerator {
20
    fn get_singular_name(&self) -> String {
3✔
21
        self.endpoint.name.to_string()
3✔
22
    }
3✔
23
    fn get_plural_name(&self) -> String {
3✔
24
        format!("{}_array", self.endpoint.name)
3✔
25
    }
3✔
26

×
27
    // Generate first secondary_index as an example
28
    fn generate_query_example(&self) -> Value {
2✔
29
        if !self.secondary_indexes.is_empty() {
2✔
30
            if let IndexDefinition::SortedInverted(fields) = &self.secondary_indexes[0] {
2✔
31
                let field_def = &self.schema.fields[fields[0]];
2✔
32
                let name = field_def.name.clone();
2✔
33
                let val = match field_def.typ {
2✔
34
                    FieldType::Int => Value::from(-1),
×
35
                    FieldType::UInt => Value::from(-1),
2✔
36
                    FieldType::Float => Value::from(1.1),
×
37
                    FieldType::Boolean => Value::from(true),
×
38
                    FieldType::String => Value::from("foo".to_string()),
×
39
                    FieldType::Binary
×
40
                    | FieldType::Decimal
41
                    | FieldType::Timestamp
42
                    | FieldType::Bson => Value::Null,
×
43

×
44
                    FieldType::Text => Value::from("lorem ipsum".to_string()),
×
45
                    FieldType::Date => Value::from("2022-11-24"),
×
46
                };
×
47
                json!({ name: val })
2✔
48
            } else {
×
49
                json!({})
×
50
            }
×
51
        } else {
52
            json!({})
×
53
        }
×
54
        // Simple expression
55
    }
2✔
56

×
57
    fn generate_get_route(&self) -> ReferenceOr<PathItem> {
1✔
58
        let responses = Responses {
1✔
59
            responses: indexmap::indexmap! {
1✔
60
                StatusCode::Code(200) =>
1✔
61
                ReferenceOr::Item(create_reference_response(format!("Get by id {}", self.endpoint.name), format!("#/components/schemas/{}", self.get_singular_name())))
1✔
62
            },
1✔
63
            ..Default::default()
1✔
64
        };
1✔
65
        let get_operation = Some(Operation {
1✔
66
            tags: vec![format!("{}", self.endpoint.name)],
1✔
67
            summary: Some("Fetch a single document record by primary key".to_owned()),
1✔
68
            description: Some(
1✔
69
                "Generated API to fetch a single record. Primary key specified will be used for lookup"
1✔
70
                    .to_owned(),
1✔
71
            ),
1✔
72
            operation_id: Some(format!("{}-by-id", self.endpoint.name)),
1✔
73
            parameters: vec![ReferenceOr::Item(Parameter::Path {
1✔
74
                parameter_data: ParameterData {
1✔
75
                    name: "id".to_owned(),
1✔
76
                    description: Some(format!("Primary key of the document - {} ", self.endpoint.index.to_owned().unwrap().primary_key.join(", "))),
1✔
77
                    required: true,
1✔
78
                    format: ParameterSchemaOrContent::Schema(ReferenceOr::Item(Schema {
1✔
79
                        schema_data: SchemaData {
1✔
80
                            ..Default::default()
1✔
81
                        },
1✔
82
                        schema_kind: SchemaKind::Type(Type::Integer(Default::default())),
1✔
83
                    })),
1✔
84
                    deprecated: None,
1✔
85
                    example: None,
1✔
86
                    examples: IndexMap::new(),
1✔
87
                    explode: None,
1✔
88
                    extensions: IndexMap::new(),
1✔
89
                },
1✔
90
                style: PathStyle::Simple,
1✔
91
            })],
1✔
92
            responses,
1✔
93
            ..Default::default()
1✔
94
        });
1✔
95
        ReferenceOr::Item(PathItem {
1✔
96
            get: get_operation,
1✔
97
            ..Default::default()
1✔
98
        })
1✔
99
    }
1✔
100

×
101
    fn generate_list_route(&self) -> ReferenceOr<PathItem> {
1✔
102
        let responses = Responses {
1✔
103
            responses: indexmap::indexmap! {
1✔
104
                StatusCode::Code(200) => ReferenceOr::Item(create_reference_response(format!("A page array of {}", self.endpoint.name.to_owned()), format!("#/components/schemas/{}",self.get_plural_name())))
1✔
105
            },
1✔
106
            ..Default::default()
1✔
107
        };
1✔
108
        let operation = Some(Operation {
1✔
109
            tags: vec![format!("{}", self.endpoint.name)],
1✔
110
            summary: Some("Fetch multiple documents in the default sort order".to_owned()),
1✔
111
            description: Some(
1✔
112
                "This is used when no filter expression or sort is needed.".to_owned(),
1✔
113
            ),
1✔
114
            operation_id: Some(format!("list-{}", self.endpoint.name.to_owned())),
1✔
115
            responses,
1✔
116
            ..Default::default()
1✔
117
        });
1✔
118
        ReferenceOr::Item(PathItem {
1✔
119
            get: operation,
1✔
120
            ..Default::default()
1✔
121
        })
1✔
122
    }
1✔
123

×
124
    fn generate_count_route(&self) -> ReferenceOr<PathItem> {
1✔
125
        let request_body = RequestBody {
1✔
126
            content: indexmap::indexmap! {
1✔
127
                "application/json".to_owned() => MediaType { example: Some(self.generate_query_example()), ..Default::default() }
1✔
128
            },
1✔
129
            required: true,
1✔
130
            ..Default::default()
1✔
131
        };
1✔
132
        let responses = Responses {
1✔
133
            responses: indexmap::indexmap! {
1✔
134
                StatusCode::Code(200) => ReferenceOr::Item(
1✔
135
                    create_response(
1✔
136
                        "Count of records satisfying the query".to_string(),
1✔
137
                        Schema {
1✔
138
                            schema_data: Default::default(),
1✔
139
                            schema_kind: SchemaKind::Type(Type::Integer(IntegerType {
1✔
140
                                format: VariantOrUnknownOrEmpty::Item(IntegerFormat::Int64),
1✔
141
                                minimum: Some(0),
1✔
142
                                ..Default::default()
1✔
143
                            })),
1✔
144
                        }
1✔
145
                    )
1✔
146
                )
1✔
147
            },
1✔
148
            ..Default::default()
1✔
149
        };
1✔
150
        let operation = Some(Operation {
1✔
151
            tags: vec![format!("{}", self.endpoint.name)],
1✔
152
            summary: Some("Count documents based on an expression".to_string()),
1✔
153
            description: Some("Count documents based on an expression".to_string()),
1✔
154
            operation_id: Some(format!("count-{}", self.endpoint.name)),
1✔
155
            request_body: Some(ReferenceOr::Item(request_body)),
1✔
156
            responses,
1✔
157
            ..Default::default()
1✔
158
        });
1✔
159
        ReferenceOr::Item(PathItem {
1✔
160
            post: operation,
1✔
161
            ..Default::default()
1✔
162
        })
1✔
163
    }
1✔
164

×
165
    fn generate_query_route(&self) -> ReferenceOr<PathItem> {
1✔
166
        let request_body = RequestBody {
1✔
167
            content: indexmap::indexmap! {
1✔
168
                "application/json".to_owned() => MediaType { example: Some(self.generate_query_example()), ..Default::default() }
1✔
169
            },
1✔
170
            required: true,
1✔
171
            ..Default::default()
1✔
172
        };
1✔
173
        let responses = Responses {
1✔
174
            responses: indexmap::indexmap! {
1✔
175
                StatusCode::Code(200) => ReferenceOr::Item(create_reference_response(format!("A page array of {}", self.endpoint.name.to_owned()), format!("#/components/schemas/{}", self.get_plural_name()) ))
1✔
176
            },
1✔
177
            ..Default::default()
1✔
178
        };
1✔
179
        let operation = Some(Operation {
1✔
180
            tags: vec![format!("{}", self.endpoint.name)],
1✔
181
            summary: Some("Query documents based on an expression".to_owned()),
1✔
182
            description: Some(
1✔
183
                "Documents can be queried based on a simple or a composite expression".to_owned(),
1✔
184
            ),
1✔
185
            operation_id: Some(format!("query-{}", self.endpoint.name)),
1✔
186
            request_body: Some(ReferenceOr::Item(request_body)),
1✔
187
            responses,
1✔
188
            ..Default::default()
1✔
189
        });
1✔
190
        ReferenceOr::Item(PathItem {
1✔
191
            post: operation,
1✔
192
            ..Default::default()
1✔
193
        })
1✔
194
    }
1✔
195

×
196
    fn _generate_available_paths(&self) -> Paths {
1✔
197
        let get_list = self.generate_list_route();
1✔
198
        let get_by_id_item = self.generate_get_route();
1✔
199
        let count_list = self.generate_count_route();
1✔
200
        let query_list = self.generate_query_route();
1✔
201
        let path_items = indexmap::indexmap! {
1✔
202
            self.endpoint.path.to_owned() => get_list,
1✔
203
            format!("{}/{}", self.endpoint.path.to_owned(), "{id}") => get_by_id_item,
1✔
204
            format!("{}/count", self.endpoint.path.to_owned()) => count_list,
1✔
205
            format!("{}/query", self.endpoint.path.to_owned()) => query_list
1✔
206
        };
1✔
207
        Paths {
1✔
208
            paths: path_items,
1✔
209
            ..Default::default()
1✔
210
        }
1✔
211
    }
1✔
212

×
213
    fn generate_component_schema(&self) -> Components {
1✔
214
        let generated_schema =
1✔
215
            convert_cache_to_oapi_schema(self.schema.to_owned(), &self.endpoint.name);
1✔
216

1✔
217
        let schemas = indexmap::indexmap! {
1✔
218
            self.get_singular_name() => ReferenceOr::Item(generated_schema),
1✔
219
            self.get_plural_name() => ReferenceOr::Item(Schema {
1✔
220
                        schema_data: SchemaData {
1✔
221
                            description: Some(format!("Array of {}", &self.endpoint.name)),
1✔
222
                            ..Default::default()
1✔
223
                        },
1✔
224
                        schema_kind: SchemaKind::Type(Type::Array(ArrayType {
1✔
225
                            items: Some(ReferenceOr::ref_(&format!("#/components/schemas/{}", self.get_singular_name()))),
1✔
226
                            min_items: None,
1✔
227
                            max_items: None,
1✔
228
                            unique_items: false,
1✔
229
                        })),
1✔
230
                    })
1✔
231
        };
1✔
232

1✔
233
        Components {
1✔
234
            schemas,
1✔
235
            ..Default::default()
1✔
236
        }
1✔
237
    }
1✔
238
}
×
239

240
impl OpenApiGenerator {
241
    pub fn generate_oas3(&self) -> Result<OpenAPI, GenerationError> {
1✔
242
        let component_schemas = self.generate_component_schema();
1✔
243
        let paths_available = self._generate_available_paths();
1✔
244

1✔
245
        let api = OpenAPI {
1✔
246
            openapi: "3.0.0".to_owned(),
1✔
247
            info: Info {
1✔
248
                title: self.endpoint.name.to_uppercase(),
1✔
249
                description: Some(format!(
1✔
250
                    "API documentation for {}. Powered by Dozer Data.",
1✔
251
                    self.endpoint.name.to_lowercase()
1✔
252
                )),
1✔
253
                version: "1.0.0".to_owned(),
1✔
254
                contact: create_contact_info(),
1✔
255
                ..Default::default()
1✔
256
            },
1✔
257
            tags: vec![Tag {
1✔
258
                name: self.endpoint.name.to_string(),
1✔
259
                ..Default::default()
1✔
260
            }],
1✔
261
            servers: self
1✔
262
                .server_host
1✔
263
                .iter()
1✔
264
                .map(|host| Server {
1✔
265
                    url: host.to_owned(),
1✔
266
                    ..Default::default()
1✔
267
                })
1✔
268
                .collect(),
1✔
269
            paths: paths_available,
1✔
270
            components: Some(component_schemas),
1✔
271
            ..Default::default()
1✔
272
        };
1✔
273
        let tmp_dir =
1✔
274
            TempDir::new("generated").map_err(|e| GenerationError::InternalError(Box::new(e)))?;
1✔
275
        let f = std::fs::OpenOptions::new()
1✔
276
            .create(true)
1✔
277
            .write(true)
1✔
278
            .open(tmp_dir.path().join("openapi.json"))
1✔
279
            .expect("Couldn't open file");
1✔
280
        serde_json::to_writer(f, &api).map_err(|e| GenerationError::InternalError(Box::new(e)))?;
1✔
281
        Ok(api)
1✔
282
    }
1✔
283

×
284
    pub fn new(
1✔
285
        schema: dozer_types::types::Schema,
1✔
286
        secondary_indexes: Vec<IndexDefinition>,
1✔
287
        endpoint: ApiEndpoint,
1✔
288
        server_host: Vec<String>,
1✔
289
    ) -> Self {
1✔
290
        Self {
1✔
291
            schema,
1✔
292
            secondary_indexes,
1✔
293
            endpoint,
1✔
294
            server_host,
1✔
295
        }
1✔
296
    }
1✔
297
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc