Skip to content

Commit

Permalink
misc code cleanup; small bugfix
Browse files Browse the repository at this point in the history
  • Loading branch information
kurtbuilds committed Nov 29, 2023
1 parent a09b43e commit f706bc0
Show file tree
Hide file tree
Showing 17 changed files with 201 additions and 58 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

108 changes: 108 additions & 0 deletions core/src/child_schemas.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
use std::collections::HashMap;
use std::sync::atomic::AtomicBool;
use openapiv3::{OpenAPI, Operation, RequestBody, Response, Schema, SchemaKind, Type};

pub trait ChildSchemas {
fn add_child_schemas<'a>(&'a self, acc: &mut HashMap<String, &'a Schema>);
}

impl ChildSchemas for Schema {
fn add_child_schemas<'a>(&'a self, acc: &mut HashMap<String, &'a Schema>) {
match &self.schema_kind {
SchemaKind::Type(Type::Array(a)) => {
let Some(items) = &a.items else { return; };
let Some(items) = items.as_item() else { return; };
let items = items.as_ref();
if let Some(title) = &items.schema_data.title {
acc.entry(title.clone()).or_insert(items);
}
items.add_child_schemas(acc);
}
SchemaKind::Type(Type::Object(o)) => {
if let Some(title) = &self.schema_data.title {
acc.entry(title.clone()).or_insert(self);
}
for (_name, prop) in &o.properties {
let Some(prop) = prop.as_item() else { continue; };
if let Some(title) = &prop.schema_data.title {
acc.entry(title.clone()).or_insert(prop);
}
prop.add_child_schemas(acc);
}
}
SchemaKind::Type(_) => {}
| SchemaKind::OneOf { one_of: schemas }
| SchemaKind::AllOf { all_of: schemas }
| SchemaKind::AnyOf { any_of: schemas} => {
for schema in schemas {
let Some(schema) = schema.as_item() else { continue; };
if let Some(title) = &schema.schema_data.title {
acc.entry(title.clone()).or_insert(schema);
}
schema.add_child_schemas(acc);
}
}
SchemaKind::Not { .. } => {}
SchemaKind::Any(_) => {}
}
}
}

impl ChildSchemas for Operation {
fn add_child_schemas<'a>(&'a self, acc: &mut HashMap<String, &'a Schema>) {
'body: {
let Some(body) = &self.request_body else { break 'body; };
let Some(body) = body.as_item() else { break 'body; };
body.add_child_schemas(acc);
}
for par in &self.parameters {
let Some(par) = par.as_item() else { continue; };
let Some(schema) = par.parameter_data_ref().schema() else { continue; };
let Some(schema) = schema.as_item() else { continue; };
schema.add_child_schemas(acc);
}
for (_code, response) in &self.responses.responses {
let Some(response) = response.as_item() else { continue; };
response.add_child_schemas(acc);
}
}
}

impl ChildSchemas for RequestBody {
fn add_child_schemas<'a>(&'a self, acc: &mut HashMap<String, &'a Schema>) {
for (_key, content) in &self.content {
let Some(schema) = &content.schema else { continue; };
let Some(schema) = schema.as_item() else { continue; };
if let Some(title) = &schema.schema_data.title {
acc.entry(title.clone()).or_insert(schema);
}
schema.add_child_schemas(acc);
}
}
}

impl ChildSchemas for Response {
fn add_child_schemas<'a>(&'a self, acc: &mut HashMap<String, &'a Schema>) {
for (k, content) in &self.content {
let Some(schema) = &content.schema else { continue; };
let Some(schema) = schema.as_item() else { continue; };
if let Some(title) = &schema.schema_data.title {
acc.entry(title.clone()).or_insert(schema);
}
schema.add_child_schemas(acc);
}
}
}

impl ChildSchemas for OpenAPI {
fn add_child_schemas<'a>(&'a self, acc: &mut HashMap<String, &'a Schema>) {
for (_path, _method, op, _item) in self.operations() {
op.add_child_schemas(acc);
}
for (name, schema) in self.schemas() {
let Some(schema) = schema.as_item() else { continue; };
acc.entry(name.clone()).or_insert(schema);
schema.add_child_schemas(acc);
}
}
}
30 changes: 9 additions & 21 deletions core/src/extractor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use tracing_ez::{span, warn};

use ln_mir::{Doc, Name, NewType};
pub use record::*;
pub use resolution::{concrete_schema_to_ty, schema_ref_to_ty, schema_ref_to_ty_already_resolved};
pub use resolution::{schema_to_ty, schema_ref_to_ty, schema_ref_to_ty_already_resolved};
pub use resolution::*;

use crate::{hir, Language, LibraryOptions};
Expand All @@ -18,12 +18,11 @@ mod resolution;
mod record;

/// You might need to call add_operation_models after this
pub fn extract_spec(spec: &OpenAPI, opt: &LibraryOptions) -> Result<MirSpec> {
pub fn extract_spec(spec: &OpenAPI) -> Result<MirSpec> {
let operations = extract_api_operations(spec)?;
let schemas = record::extract_records(spec)?;

let schemas = extract_records(spec)?;
let servers = extract_servers(spec)?;
let security = extract_security_strategies(spec, opt);
let security = extract_security_strategies(spec);

let api_docs_url = extract_api_docs_link(spec);

Expand Down Expand Up @@ -351,7 +350,7 @@ pub fn spec_defines_auth(spec: &MirSpec) -> bool {
!spec.security.is_empty()
}

fn extract_security_fields(_name: &str, requirement: &SecurityRequirement, spec: &OpenAPI, opt: &LibraryOptions) -> Result<Vec<AuthorizationParameter>> {
fn extract_security_fields(_name: &str, requirement: &SecurityRequirement, spec: &OpenAPI) -> Result<Vec<AuthorizationParameter>> {
let security_schemas = &spec.components.as_ref().unwrap().security_schemes;
let mut fields = vec![];
for (name, _scopes) in requirement {
Expand Down Expand Up @@ -403,33 +402,22 @@ fn extract_security_fields(_name: &str, requirement: &SecurityRequirement, spec:

fields.push(AuthorizationParameter {
name: name.to_string(),
env_var: if name
.to_lowercase()
.starts_with(&opt.service_name.to_lowercase())
{
name.to_case(Case::ScreamingSnake)
} else {
format!(
"{}_{}",
opt.service_name.to_case(Case::ScreamingSnake),
name.to_case(Case::ScreamingSnake)
)
},
env_var: name.to_case(Case::ScreamingSnake),
location,
});
}
Ok(fields)
}

pub fn extract_security_strategies(spec: &OpenAPI, opt: &LibraryOptions) -> Vec<AuthorizationStrategy> {
pub fn extract_security_strategies(spec: &OpenAPI) -> Vec<AuthorizationStrategy> {
let mut strats = vec![];
let security = match spec.security.as_ref() {
None => return strats,
Some(s) => s,
};
for requirement in security {
let (name, _scopes) = requirement.iter().next().unwrap();
let fields = match extract_security_fields(name, requirement, spec, opt) {
let fields = match extract_security_fields(name, requirement, spec) {
Ok(f) => f,
Err(_e) => {
continue;
Expand All @@ -444,7 +432,7 @@ pub fn extract_security_strategies(spec: &OpenAPI, opt: &LibraryOptions) -> Vec<
}

pub fn extract_newtype(name: &str, schema: &Schema, spec: &OpenAPI) -> NewType<Ty> {
let ty = concrete_schema_to_ty(schema, spec);
let ty = schema_to_ty(schema, spec);

NewType {
name: name.to_string(),
Expand Down
31 changes: 13 additions & 18 deletions core/src/extractor/record.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
/// Records are the "model"s of the MIR world. model is a crazy overloaded word though.
use openapiv3::{ObjectType, OpenAPI, ReferenceOr, Schema, SchemaData, SchemaKind, SchemaReference, StringType, Type};
use openapiv3::{ObjectType, OpenAPI, ReferenceOr, Schema, SchemaData, SchemaKind, SchemaReference, StatusCode, StringType, Type};
use ln_mir::{Doc, Name};
use std::collections::{BTreeMap};
use std::collections::{BTreeMap, HashMap};
use tracing_ez::warn;
use crate::{extractor, hir};
use crate::extractor::schema_ref_to_ty_already_resolved;
use crate::extractor::{schema_to_ty, schema_ref_to_ty_already_resolved};
use crate::hir::{MirField, Record, StrEnum, Struct};
use indexmap::IndexMap;
use anyhow::Result;
use crate::child_schemas::ChildSchemas;

fn properties_to_fields(properties: &IndexMap<String, ReferenceOr<Schema>>, schema: &Schema, spec: &OpenAPI) -> BTreeMap<Name, MirField> {
properties
Expand Down Expand Up @@ -44,8 +45,7 @@ pub fn effective_length(all_of: &[ReferenceOr<Schema>]) -> usize {
length
}

pub fn create_record(name: &str, schema_ref: &ReferenceOr<Schema>, spec: &OpenAPI) -> Record {
let schema = schema_ref.resolve(spec);
pub fn create_record(name: &str, schema: &Schema, spec: &OpenAPI) -> Record {
match &schema.schema_kind {
// The base case, a regular object
SchemaKind::Type(Type::Object(ObjectType { properties, .. })) => {
Expand Down Expand Up @@ -80,7 +80,7 @@ pub fn create_record(name: &str, schema_ref: &ReferenceOr<Schema>, spec: &OpenAP
_ => Record::NewType(hir::NewType {
name: Name::new(name),
fields: vec![MirField {
ty: schema_ref_to_ty_already_resolved(schema_ref, spec, schema),
ty: schema_to_ty(schema, spec),
optional: schema.schema_data.nullable,
doc: None,
example: None,
Expand Down Expand Up @@ -142,19 +142,14 @@ fn create_record_from_all_of(name: &str, all_of: &[ReferenceOr<Schema>], schema_

// records are data types: structs, newtypes
pub fn extract_records(spec: &OpenAPI) -> Result<BTreeMap<String, Record>> {
if spec.components.is_none() {
return Ok(BTreeMap::new());
let mut result: BTreeMap<String, Record> = BTreeMap::new();
let mut schema_lookup = HashMap::new();
spec.add_child_schemas(&mut schema_lookup);
for (name, schema) in schema_lookup {
let rec = create_record(&name, schema, spec);
let name = rec.name().0.clone();
result.insert(name, rec);
}
let result: BTreeMap<String, Record> = spec.schemas()
.into_iter()
.map(|(name, schema)| {
create_record(name, schema, spec)
})
.map(|r| {
let name = r.name().0.clone();
Ok((name, r))
})
.collect::<Result<_>>()?;
Ok(result)
}

Expand Down
14 changes: 10 additions & 4 deletions core/src/extractor/resolution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ pub fn schema_ref_to_ty(schema_ref: &ReferenceOr<Schema>, spec: &OpenAPI) -> Ty

pub fn schema_ref_to_ty_already_resolved(schema_ref: &ReferenceOr<Schema>, spec: &OpenAPI, schema: &Schema) -> Ty {
if is_primitive(schema, spec) {
concrete_schema_to_ty(schema, spec)
schema_to_ty(schema, spec)
} else {
match schema_ref {
ReferenceOr::Reference { reference } => {
Expand All @@ -22,14 +22,14 @@ pub fn schema_ref_to_ty_already_resolved(schema_ref: &ReferenceOr<Schema>, spec:
SchemaReference::Property { schema: _, property: _ } => unimplemented!(),
}
}
ReferenceOr::Item(schema) => concrete_schema_to_ty(schema, spec)
ReferenceOr::Item(schema) => schema_to_ty(schema, spec)
}
}
}

/// You probably want schema_ref_to_ty, not this method. Reason being, you want
/// to use the ref'd model if one exists (e.g. User instead of resolving to Ty::Any)
pub fn concrete_schema_to_ty(schema: &Schema, spec: &OpenAPI) -> Ty {
pub fn schema_to_ty(schema: &Schema, spec: &OpenAPI) -> Ty {
match &schema.schema_kind {
SchemaKind::Type(oa::Type::String(s)) => {
match s.format.as_str() {
Expand Down Expand Up @@ -59,7 +59,13 @@ pub fn concrete_schema_to_ty(schema: &Schema, spec: &OpenAPI) -> Ty {
}
}
SchemaKind::Type(oa::Type::Boolean {}) => Ty::Boolean,
SchemaKind::Type(oa::Type::Object(_)) => Ty::Any,
SchemaKind::Type(oa::Type::Object(_)) => {
if let Some(title) = &schema.schema_data.title {
Ty::model(&title)
} else {
Ty::Any
}
},
SchemaKind::Type(oa::Type::Array(ArrayType {
items: Some(item), ..
})) => {
Expand Down
14 changes: 12 additions & 2 deletions core/src/hir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ impl Default for Ty {
fn default() -> Self {
Ty::Any
}

}

impl Ty {
Expand Down Expand Up @@ -177,6 +176,17 @@ pub struct AuthorizationParameter {
pub location: AuthLocation,
}

impl AuthorizationParameter {
pub fn env_var_for_service(&self, service_name: &str) -> String {
let service = service_name.to_case(Case::ScreamingSnake);
if self.env_var.starts_with(&service) {
self.env_var.clone()
} else {
format!("{}_{}", service, self.env_var)
}
}
}

#[derive(Debug, Clone)]
pub enum AuthLocation {
Header { key: String },
Expand Down Expand Up @@ -354,7 +364,7 @@ impl MirSpec {
}
for strategy in &self.security {
for param in &strategy.fields {
env_vars.push(param.env_var.clone());
env_vars.push(param.env_var_for_service(&opt.service_name));
}
}
env_vars
Expand Down
1 change: 1 addition & 0 deletions core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ mod lang;
mod options;
pub mod extractor;
mod template;
pub mod child_schemas;

pub use options::*;
pub use lang::Language;
Expand Down
3 changes: 3 additions & 0 deletions libninja/src/bin/libninja.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ pub enum Command {
/// `gen` will not work if the spec is split into multiple files, so use this step first if the
/// spec is split.
Coalesce(Resolve),
/// Analyze the OpenAPI spec
Meta(Meta),
}

fn main() -> Result<()> {
Expand Down Expand Up @@ -72,5 +74,6 @@ fn main() -> Result<()> {
generate.run()
},
Command::Coalesce(resolve) => resolve.run(),
Command::Meta(meta) => meta.run(),
}
}
2 changes: 2 additions & 0 deletions libninja/src/command.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
mod generate;
mod resolve;
mod meta;

use anyhow::anyhow;
pub use generate::*;
pub use resolve::*;
pub use meta::*;

pub trait Success {
fn ok(&self) -> anyhow::Result<()>;
Expand Down
Loading

0 comments on commit f706bc0

Please sign in to comment.