From 1a5e94dd86d0039c2bd78213326db6509e77131a Mon Sep 17 00:00:00 2001
From: Maaz Ahmed <maaz.a@subcom.tech>
Date: Mon, 12 Feb 2024 15:18:56 +0530
Subject: [PATCH] chore(docs): update documentation

---
 Cargo.toml |  2 ++
 README.md  | 50 +++++++++++++++++++++++++++++++++++++++++++++++++
 src/lib.rs | 55 +++++++++++++++++++++++++++++++++++++++++++++++-------
 3 files changed, 100 insertions(+), 7 deletions(-)
 create mode 100644 README.md

diff --git a/Cargo.toml b/Cargo.toml
index dae8f76..a8db2bb 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -8,4 +8,6 @@ edition = "2021"
 [dependencies]
 jsonschema = {version = "0.17.1", default-features = false, features = ["draft202012"]}
 serde_json = "1.0.113"
+
+[dev-dependencies]
 utoipa = "4.2.0"
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..253ee60
--- /dev/null
+++ b/README.md
@@ -0,0 +1,50 @@
+# Schema Police
+This library provides the [`SchemaInspector`] type which simplifies building a JSON validator from the schemas present in the OpenAPI spec (tested on OpenAPI spec 3.0.3 wich uses the Json Schema draft 202012). 
+
+The primary goal of this libary is to provide the necessary glue to make it possible to use the OpenAPI spec generated by the [utoipa](https://crates.io/crates/utoipa) crate with the [jsonschema](https://crates.io/crates/jsonschema) schema validation library. However, this approach may become unnessary if local references start to get resolved correctly in the `jsonschema` crate (which currently results in a `invalid reference` error).
+
+## Why the name `Schema Police`?
+It's a reference to the Radiohead song 'Karma Police'
+
+## Usage Example
+```rust
+use schema_police::SchemaInspector;
+use utoipa::OpenApi;
+
+fn main() {
+    // Get the schemas from OpenAPI spec generated by utoipa
+    let schemas = serde_json::to_value(ApiDoc::openapi().components.unwrap().schemas).unwrap();
+    // Initialize JSON inspector  for the `ExampleSchema` type
+    let inspector = SchemaInspector::new_infer::<ExampleSchema>(&schemas, false).unwrap();
+    // Example JSON request that contains errors
+    let example_request = serde_json::json!({
+        "integer": 10,
+        "string": "hello",
+        "array": ["one"]
+    });
+
+    let Err(result) = inspector.inspect(&example_request) else {
+        panic!("This should be an error!");
+    };
+
+    assert_eq!(
+        result.to_string(),
+        r#"JSON doesn't match expected schema: ["one"] has less than 2 items, 10 is less than the minimum of 15"#
+    );
+}
+
+#[derive(utoipa::OpenApi)]
+#[openapi(components(schemas(ExampleSchema)))]
+struct ApiDoc;
+
+#[allow(unused)]
+#[derive(utoipa::ToSchema)]
+struct ExampleSchema {
+    #[schema(minimum = 15, maximum = 90)]
+    integer: i32,
+    #[schema(pattern = "^hello.*")]
+    string: String,
+    #[schema(min_items = 2)]
+    array: Vec<String>,
+}
+```
diff --git a/src/lib.rs b/src/lib.rs
index ad29848..cde9123 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,17 +1,48 @@
+#[doc = include_str!("../README.md")]
 use jsonschema::{Draft, JSONSchema};
 use serde_json::{Map, Value};
 use std::fmt::Display;
 
-pub struct Inspector {
-    schema: JSONSchema,
+/// The JSON schema inspector (validator)
+///
+/// This contains the necessary glue to make all the schemas from the OpenAPI spec (tested on 3.0.3) work with the `jsonschema` crate.
+/// This is achieved by simply resolving the local references present in the schemas with the actual schemas.
+///
+/// It is recommended to initialize one `SchemaInspector` for one schema only once, and use the same instance to validate/inspect
+/// multiple JSON objects to avoid performance overhead of compiling the schema multiple times.
+///
+/// Note: Resolving references is optional and is not required for schemas that do not contain any references to nested schemas.
+#[derive(Debug)]
+pub struct SchemaInspector {
+    schema: JSONSchema, // the compiled schema used to validate JSON
 }
 
-impl Inspector {
+impl SchemaInspector {
     const PROP: &'static str = "properties";
     const REF: &'static str = "$ref";
     const AOF: &'static str = "allOf";
 
-    /// Initialize an Inspector using the name of the type provided to the function using the turbofish syntax
+    /// Initialize the `SchemaInspector` using the name of the type provided to the function using the turbofish syntax instead of using a string
+    ///
+    /// Note: Resolving references is optional and is not required for schemas that do not contain any references to nested schemas.
+    /// It is recommended to set `resolve_refs` to false when there are no references present in the schema to avoid extra overhead.
+    ///
+    /// ## Example
+    /// ```
+    /// use schema_police::{SchemaInspector, InitError};
+    ///
+    /// fn construct_inspector(schema: &serde_json::Value) -> Result<SchemaInspector, InitError> {
+    ///     SchemaInspector::new_infer::<SchemaType>(schema, true)
+    /// }
+    ///
+    /// #[derive(utoipa::ToSchema)]
+    /// struct SchemaType {
+    ///     field: String,
+    /// }
+    /// ```
+    ///
+    /// Note: the function internally uses [`std::any::type_name`] to get the name of the type. Accodring to the std lib docs, the function
+    /// is meant to be used for debugging and makes no guarantees. Therefore, it is not recommended to rely on this method in mission critical contexts.
     pub fn new_infer<T>(schemas: &Value, resolve_refs: bool) -> Result<Self, InitError> {
         // get target name from type inference
         let target = std::any::type_name::<T>()
@@ -21,7 +52,10 @@ impl Inspector {
         Self::new(target, schemas, resolve_refs)
     }
 
-    /// Construct an instance of a schema `Inspector` using the provided OpenAPI spec's schemas (in the form of `serde_json::Value`)
+    /// Construct an instance of the `SchemaInspector` using the provided OpenAPI spec's schemas (in the form of `serde_json::Value`)
+    ///
+    /// Note: Resolving references is optional and is not required for schemas that do not contain any references to nested schemas.
+    /// It is recommended to set `resolve_refs` to false when there are no references present in the schema to avoid extra overhead.
     pub fn new(target: &str, schemas: &Value, resolve_refs: bool) -> Result<Self, InitError> {
         use InitError::*;
         // extract schemas object
@@ -56,7 +90,9 @@ impl Inspector {
         })
     }
 
-    // Replace internal '$ref' references with the correct schemas
+    /// Replace internal '$ref' references with the correct schemas
+    /// The current implementation disregards the paths in the references and only uses the schema names
+    /// to retrieve them from the root schemas object
     fn resolve_references(
         schemas: &Map<String, Value>,
         props: &mut Map<String, Value>,
@@ -87,7 +123,7 @@ impl Inspector {
         Ok(())
     }
 
-    // Extract the fragment and pack it with the given field name
+    /// Extract the fragment and pack it with the given field name
     #[inline]
     fn extract_ref(val: &Value) -> Option<String> {
         let refr = val.get(Self::REF)?;
@@ -115,6 +151,10 @@ impl Inspector {
     }
 }
 
+/// The error that is returned by the [`SchemaInspector`] when the provided JSON to the `inspect` function
+/// fails to conform to the schema the inspector was compiled with.
+///
+/// The error type internally holds a string containing detailed information about what part of the JSON differs from the schema
 #[derive(Debug)]
 pub struct SchemaError {
     cause: String,
@@ -128,6 +168,7 @@ impl Display for SchemaError {
     }
 }
 
+/// All the possible errors that can occur while initializing [`SchemaInspector`]
 #[derive(Debug)]
 pub enum InitError {
     SchemaNotFound(String),
-- 
GitLab